mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-11 20:21:02 +00:00
Compare commits
63 Commits
actions-ru
...
gh-pages
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2d3489171 | ||
|
|
90db051e3e | ||
|
|
41e135be59 | ||
|
|
11938d728d | ||
|
|
bfd63fb6f9 | ||
|
|
c70e760b19 | ||
|
|
3c7e32eb9f | ||
|
|
ccc7b81b5c | ||
|
|
701f8427a0 | ||
|
|
20322eb2c9 | ||
|
|
72edcbba10 | ||
|
|
5396f9322b | ||
|
|
65b0cdc588 | ||
|
|
b7dbf997ec | ||
|
|
c04f1daeab | ||
|
|
84b7abe2ce | ||
|
|
85422d15a8 | ||
|
|
d3e9c43c34 | ||
|
|
6a9b0d74fd | ||
|
|
3e1fbfa830 | ||
|
|
c842be4501 | ||
|
|
132867482f | ||
|
|
6a2a90164f | ||
|
|
f7952743e5 | ||
|
|
1492a0d0f9 | ||
|
|
26d9758452 | ||
|
|
6ac125f060 | ||
|
|
48af148297 | ||
|
|
818c1bd3dd | ||
|
|
a4c569f552 | ||
|
|
55d5550ad4 | ||
|
|
ddc29b1d38 | ||
|
|
b684553da2 | ||
|
|
ec8a74f219 | ||
|
|
73e6a91de3 | ||
|
|
e1c62ee5e5 | ||
|
|
a192a76ca9 | ||
|
|
a7d378ca09 | ||
|
|
285cfd69cd | ||
|
|
c1fb952a94 | ||
|
|
b1916a0e1a | ||
|
|
44972a284c | ||
|
|
6dd93508e7 | ||
|
|
930efd244d | ||
|
|
60f577ea04 | ||
|
|
31a16d3c2e | ||
|
|
c53a03372d | ||
|
|
e9caad7dec | ||
|
|
7a21693912 | ||
|
|
942fc9fe00 | ||
|
|
a2096046d5 | ||
|
|
a7cb21605c | ||
|
|
c495ce47ed | ||
|
|
f1a1941455 | ||
|
|
a19eab8382 | ||
|
|
4ee7e5541f | ||
|
|
013d5bd2b2 | ||
|
|
c1d36ebaef | ||
|
|
71eb2ae333 | ||
|
|
dd1ad63ca9 | ||
|
|
de7e37509c | ||
|
|
51918fecbe | ||
|
|
4fb7d154d6 |
@@ -1,5 +1,3 @@
|
|||||||
name: Build and Release Runners
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
@@ -16,8 +14,6 @@ on:
|
|||||||
- runner/dindrunner.Dockerfile
|
- runner/dindrunner.Dockerfile
|
||||||
- runner/entrypoint.sh
|
- runner/entrypoint.sh
|
||||||
- .github/workflows/build-runner.yml
|
- .github/workflows/build-runner.yml
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
name: Runner
|
name: Runner
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -31,14 +27,10 @@ jobs:
|
|||||||
- name: actions-runner-dind
|
- name: actions-runner-dind
|
||||||
dockerfile: dindrunner.Dockerfile
|
dockerfile: dindrunner.Dockerfile
|
||||||
env:
|
env:
|
||||||
RUNNER_VERSION: 2.276.1
|
RUNNER_VERSION: 2.275.1
|
||||||
DOCKER_VERSION: 19.03.12
|
DOCKER_VERSION: 19.03.12
|
||||||
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set outputs
|
|
||||||
id: vars
|
|
||||||
run: echo ::set-output name=sha_short::${GITHUB_SHA::7}
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
@@ -57,35 +49,16 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
# Considered unstable builds
|
- name: Build [and Push]
|
||||||
# Mutable (no sha) and immutable (include sha) tags are created, see Issue 285 and PR 286 for why
|
|
||||||
- name: Build and push canary builds
|
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: ./runner
|
context: ./runner
|
||||||
file: ./runner/${{ matrix.dockerfile }}
|
file: ./runner/${{ matrix.dockerfile }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: ${{ github.event_name != 'pull_request' && github.event_name != 'release' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
build-args: |
|
|
||||||
RUNNER_VERSION=${{ env.RUNNER_VERSION }}
|
|
||||||
DOCKER_VERSION=${{ env.DOCKER_VERSION }}
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-canary
|
|
||||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-canary-${{ steps.vars.outputs.sha_short }}
|
|
||||||
|
|
||||||
# Considered stable builds
|
|
||||||
# Mutable (no sha) and immutable (include sha) tags are created, see Issue 285 and PR 286 for why
|
|
||||||
- name: Build and push release builds
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: ./runner
|
|
||||||
file: ./runner/${{ matrix.dockerfile }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: ${{ github.event_name == 'release' }}
|
|
||||||
build-args: |
|
build-args: |
|
||||||
RUNNER_VERSION=${{ env.RUNNER_VERSION }}
|
RUNNER_VERSION=${{ env.RUNNER_VERSION }}
|
||||||
DOCKER_VERSION=${{ env.DOCKER_VERSION }}
|
DOCKER_VERSION=${{ env.DOCKER_VERSION }}
|
||||||
tags: |
|
tags: |
|
||||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}
|
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}
|
||||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ steps.vars.outputs.sha_short }}
|
|
||||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest
|
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest
|
||||||
75
.github/workflows/on-push-lint-charts.yml
vendored
75
.github/workflows/on-push-lint-charts.yml
vendored
@@ -1,75 +0,0 @@
|
|||||||
name: Lint and Test Charts
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'charts/**'
|
|
||||||
- '.github/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
KUBE_SCORE_VERSION: 1.10.0
|
|
||||||
HELM_VERSION: v3.4.1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Helm
|
|
||||||
uses: azure/setup-helm@v1
|
|
||||||
with:
|
|
||||||
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)
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
|
|
||||||
- name: Set up chart-testing
|
|
||||||
uses: helm/chart-testing-action@v2.0.1
|
|
||||||
|
|
||||||
- name: Run chart-testing (list-changed)
|
|
||||||
id: list-changed
|
|
||||||
run: |
|
|
||||||
changed=$(ct list-changed --config charts/.ci/ct-config.yaml)
|
|
||||||
if [[ -n "$changed" ]]; then
|
|
||||||
echo "::set-output name=changed::true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run chart-testing (lint)
|
|
||||||
run: ct lint --config charts/.ci/ct-config.yaml
|
|
||||||
|
|
||||||
- name: Create kind cluster
|
|
||||||
uses: helm/kind-action@v1.0.0
|
|
||||||
if: steps.list-changed.outputs.changed == 'true'
|
|
||||||
|
|
||||||
# We need cert-manager already installed in the cluster because we assume the CRDs exist
|
|
||||||
- name: Install cert-manager
|
|
||||||
run: |
|
|
||||||
helm repo add jetstack https://charts.jetstack.io --force-update
|
|
||||||
helm install cert-manager jetstack/cert-manager --set installCRDs=true --wait
|
|
||||||
if: steps.list-changed.outputs.changed == 'true'
|
|
||||||
|
|
||||||
- name: Run chart-testing (install)
|
|
||||||
run: ct install --config charts/.ci/ct-config.yaml
|
|
||||||
101
.github/workflows/on-push-master-publish-chart.yml
vendored
101
.github/workflows/on-push-master-publish-chart.yml
vendored
@@ -1,101 +0,0 @@
|
|||||||
name: Publish helm chart
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- main # assume that the branch name may change in future
|
|
||||||
paths:
|
|
||||||
- 'charts/**'
|
|
||||||
- '.github/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
KUBE_SCORE_VERSION: 1.10.0
|
|
||||||
HELM_VERSION: v3.4.1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-chart:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Helm
|
|
||||||
uses: azure/setup-helm@v1
|
|
||||||
with:
|
|
||||||
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)
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.7
|
|
||||||
|
|
||||||
- name: Set up chart-testing
|
|
||||||
uses: helm/chart-testing-action@v2.0.1
|
|
||||||
|
|
||||||
- name: Run chart-testing (list-changed)
|
|
||||||
id: list-changed
|
|
||||||
run: |
|
|
||||||
changed=$(ct list-changed --config charts/.ci/ct-config.yaml)
|
|
||||||
if [[ -n "$changed" ]]; then
|
|
||||||
echo "::set-output name=changed::true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run chart-testing (lint)
|
|
||||||
run: ct lint --config charts/.ci/ct-config.yaml
|
|
||||||
|
|
||||||
- name: Create kind cluster
|
|
||||||
uses: helm/kind-action@v1.0.0
|
|
||||||
if: steps.list-changed.outputs.changed == 'true'
|
|
||||||
|
|
||||||
# We need cert-manager already installed in the cluster because we assume the CRDs exist
|
|
||||||
- name: Install cert-manager
|
|
||||||
run: |
|
|
||||||
helm repo add jetstack https://charts.jetstack.io --force-update
|
|
||||||
helm install cert-manager jetstack/cert-manager --set installCRDs=true --wait
|
|
||||||
if: steps.list-changed.outputs.changed == 'true'
|
|
||||||
|
|
||||||
- name: Run chart-testing (install)
|
|
||||||
run: ct install --config charts/.ci/ct-config.yaml
|
|
||||||
if: steps.list-changed.outputs.changed == 'true'
|
|
||||||
|
|
||||||
publish-chart:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: lint-chart
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure Git
|
|
||||||
run: |
|
|
||||||
git config user.name "$GITHUB_ACTOR"
|
|
||||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
|
||||||
|
|
||||||
- name: Run chart-releaser
|
|
||||||
uses: helm/chart-releaser-action@v1.1.0
|
|
||||||
env:
|
|
||||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
|
|
||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -9,10 +9,6 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
DOCKERHUB_USERNAME: ${{ github.repository_owner }}
|
||||||
steps:
|
steps:
|
||||||
- name: Set outputs
|
|
||||||
id: vars
|
|
||||||
run: echo ::set-output name=sha_short::${GITHUB_SHA::7}
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
@@ -56,7 +52,5 @@ jobs:
|
|||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: ${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}
|
||||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}
|
|
||||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}-${{ steps.vars.outputs.sha_short }}
|
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,3 @@ bin
|
|||||||
|
|
||||||
.envrc
|
.envrc
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_STORE
|
|
||||||
@@ -22,8 +22,7 @@ COPY . .
|
|||||||
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
||||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
||||||
GOARM=$(echo ${TARGETPLATFORM} | cut -d / -f3 | cut -c2-) && \
|
GOARM=$(echo ${TARGETPLATFORM} | cut -d / -f3 | cut -c2-) && \
|
||||||
go build -a -o manager main.go && \
|
go build -a -o manager main.go
|
||||||
go build -a -o github-webhook-server ./cmd/githubwebhookserver
|
|
||||||
|
|
||||||
# Use distroless as minimal base image to package the manager binary
|
# Use distroless as minimal base image to package the manager binary
|
||||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||||
@@ -32,7 +31,6 @@ FROM gcr.io/distroless/static:nonroot
|
|||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
COPY --from=builder /workspace/manager .
|
COPY --from=builder /workspace/manager .
|
||||||
COPY --from=builder /workspace/github-webhook-server .
|
|
||||||
|
|
||||||
USER nonroot:nonroot
|
USER nonroot:nonroot
|
||||||
|
|
||||||
|
|||||||
222
README.md
222
README.md
@@ -2,30 +2,6 @@
|
|||||||
|
|
||||||
This controller operates self-hosted runners for GitHub Actions on your Kubernetes cluster.
|
This controller operates self-hosted runners for GitHub Actions on your Kubernetes cluster.
|
||||||
|
|
||||||
ToC:
|
|
||||||
|
|
||||||
- [Motivation](#motivation)
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [GitHub Enterprise support](#github-enterprise-support)
|
|
||||||
- [Setting up authentication with GitHub API](#setting-up-authentication-with-github-api)
|
|
||||||
- [Using GitHub App](#using-github-app)
|
|
||||||
- [Using Personal AccessToken ](#using-personal-access-token)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Repository Runners](#repository-runners)
|
|
||||||
- [Organization Runners](#organization-runners)
|
|
||||||
- [Runner Deployments](#runnerdeployments)
|
|
||||||
- [Autoscaling](#autoscaling)
|
|
||||||
- [Faster Autoscaling with GitHub Webhook](#faster-autoscaling-with-github-webhook)
|
|
||||||
- [Runner with DinD](#runner-with-dind)
|
|
||||||
- [Additional tweaks](#additional-tweaks)
|
|
||||||
- [Runner labels](#runner-labels)
|
|
||||||
- [Runer groups](#runner-groups)
|
|
||||||
- [Using EKS IAM role for service accounts](#using-eks-iam-role-for-service-accounts)
|
|
||||||
- [Software installed in the runner image](#software-installed-in-the-runner-image)
|
|
||||||
- [Common errors](#common-errors)
|
|
||||||
- [Developing](#developing)
|
|
||||||
- [Alternatives](#alternatives)
|
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
[GitHub Actions](https://github.com/features/actions) is a very useful tool for automating development. GitHub Actions jobs are run in the cloud by default, but you may want to run your jobs in your environment. [Self-hosted runner](https://github.com/actions/runner) can be used for such use cases, but requires the provisioning and configuration of a virtual machine instance. Instead if you already have a Kubernetes cluster, it makes more sense to run the self-hosted runner on top of it.
|
[GitHub Actions](https://github.com/features/actions) is a very useful tool for automating development. GitHub Actions jobs are run in the cloud by default, but you may want to run your jobs in your environment. [Self-hosted runner](https://github.com/actions/runner) can be used for such use cases, but requires the provisioning and configuration of a virtual machine instance. Instead if you already have a Kubernetes cluster, it makes more sense to run the self-hosted runner on top of it.
|
||||||
@@ -38,71 +14,21 @@ actions-runner-controller uses [cert-manager](https://cert-manager.io/docs/insta
|
|||||||
|
|
||||||
- [Installing cert-manager on Kubernetes](https://cert-manager.io/docs/installation/kubernetes/)
|
- [Installing cert-manager on Kubernetes](https://cert-manager.io/docs/installation/kubernetes/)
|
||||||
|
|
||||||
Install the custom resource and actions-runner-controller with `kubectl` or `helm`. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources.
|
Install the custom resource and actions-runner-controller itself. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources.
|
||||||
|
|
||||||
`kubectl`:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# REPLACE "v0.16.1" with the latest release
|
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml
|
||||||
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/download/v0.16.1/actions-runner-controller.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
`helm`:
|
|
||||||
|
|
||||||
```
|
|
||||||
helm repo add actions-runner-controller https://summerwind.github.io/actions-runner-controller
|
|
||||||
helm upgrade --install -n actions-runner-system actions-runner-controller/actions-runner-controller
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Github Enterprise support
|
### Github Enterprise support
|
||||||
|
|
||||||
If you use either Github Enterprise Cloud or Server, you can use **actions-runner-controller** with those, too.
|
If you use either Github Enterprise Cloud or Server (and have recent enought version supporting Actions), you can use **actions-runner-controller** with those, too. Authentication works same way as with public Github (repo and organization level).
|
||||||
Authentication works same way as with public Github (repo and organization level).
|
|
||||||
The minimum version of Github Enterprise Server is 3.0.0 (or rc1/rc2).
|
|
||||||
In most cases maintainers do not have environment where to test changes and are reliant on the community for testing.
|
|
||||||
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system
|
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Enterprise runners usage
|
[Enterprise level](https://docs.github.com/en/enterprise-server@2.22/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-an-enterprise) runners are not working yet as there's no API definition for those.
|
||||||
|
|
||||||
In order to use enterprise runners you must have Admin access to Github Enterprise and you should do Personal Access Token (PAT)
|
|
||||||
with `enterprise:admin` access. Enterprise runners are not possible to run with Github APP or any other permission.
|
|
||||||
|
|
||||||
When you use enterprise runners those will get access to Github Organisations. However, access to the repositories is **NOT**
|
|
||||||
allowed by default. Each Github Organisation must allow Enterprise runner groups to be used in repositories.
|
|
||||||
This is needed only one time and is permanent after that.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: actions.summerwind.dev/v1alpha1
|
|
||||||
kind: RunnerDeployment
|
|
||||||
metadata:
|
|
||||||
name: ghe-runner-deployment
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
enterprise: your-enterprise-name
|
|
||||||
dockerdWithinRunnerContainer: true
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: "4000m"
|
|
||||||
memory: "2Gi"
|
|
||||||
requests:
|
|
||||||
cpu: "200m"
|
|
||||||
memory: "200Mi"
|
|
||||||
volumeMounts:
|
|
||||||
- mountPath: /runner
|
|
||||||
name: runner
|
|
||||||
volumes:
|
|
||||||
- name: runner
|
|
||||||
emptyDir: {}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setting up authentication with GitHub API
|
## Setting up authentication with GitHub API
|
||||||
|
|
||||||
@@ -363,119 +289,7 @@ spec:
|
|||||||
scaleDownFactor: '0.7'
|
scaleDownFactor: '0.7'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Faster Autoscaling with GitHub Webhook
|
## Runner with DinD
|
||||||
|
|
||||||
> This feature is an ADVANCED feature which may require more work to set up.
|
|
||||||
> Please get prepared to put some time and effort to learn and leverage this feature!
|
|
||||||
|
|
||||||
`actions-runner-controller` has an optional Webhook server that receives GitHub Webhook events and scale
|
|
||||||
[`RunnerDeployment`s](#runnerdeployments) by updating corresponding [`HorizontalRunnerAutoscaler`s](#autoscaling).
|
|
||||||
|
|
||||||
Today, the Webhook server can be configured to respond GitHub `check_run`, `pull_request`, and `push` events
|
|
||||||
by scaling up the matching `HorizontalRunnerAutoscaler` by N replica(s), where `N` is configurable within
|
|
||||||
`HorizontalRunerAutoscaler`'s `Spec`.
|
|
||||||
|
|
||||||
More concretely, you can configure the targeted GitHub event types and the `N` in
|
|
||||||
`scaleUpTriggers`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
kind: HorizontalRunnerAutoscaler
|
|
||||||
spec:
|
|
||||||
scaleTargetRef:
|
|
||||||
name: myrunners
|
|
||||||
scaleUpTrigggers:
|
|
||||||
- githubEvent:
|
|
||||||
checkRun:
|
|
||||||
types: ["created"]
|
|
||||||
status: "queued"
|
|
||||||
amount: 1
|
|
||||||
duration: "5m"
|
|
||||||
```
|
|
||||||
|
|
||||||
With the above example, the webhook server scales `myrunners` by `1` replica for 5 minutes on each `check_run` event
|
|
||||||
with the type of `created` and the status of `queued` received.
|
|
||||||
|
|
||||||
The primary benefit of autoscaling on Webhook compared to the standard autoscaling is that this one allows you to
|
|
||||||
immediately add "resource slack" for future GitHub Actions job runs.
|
|
||||||
|
|
||||||
In contrast, the standard autoscaling requires you to wait next sync period to add
|
|
||||||
insufficient runners. You can definitely shorten the sync period to make the standard autoscaling more responsive.
|
|
||||||
But doing so eventually result in the controller not functional due to GitHub API rate limit.
|
|
||||||
|
|
||||||
> You can learn the implementation details in #282
|
|
||||||
|
|
||||||
To enable this feature, you firstly need to install the webhook server.
|
|
||||||
|
|
||||||
Currently, only our Helm chart has the ability install it.
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ helm --upgrade install actions-runner-controller/actions-runner-controller \
|
|
||||||
githubWebhookServer.enabled=true \
|
|
||||||
githubWebhookServer.ports[0].nodePort=33080
|
|
||||||
```
|
|
||||||
|
|
||||||
The above command will result in exposing the node port 33080 for Webhook events. Usually, you need to create an
|
|
||||||
external loadbalancer targeted to the node port, and register the hostname or the IP address of the external loadbalancer
|
|
||||||
to the GitHub Webhook.
|
|
||||||
|
|
||||||
Once you were able to confirm that the Webhook server is ready and running from GitHub - this is usually verified by the
|
|
||||||
GitHub sending PING events to the Webhook server - create or update your `HorizontalRunnerAutoscaler` resources
|
|
||||||
by learning the following configuration examples.
|
|
||||||
|
|
||||||
- [Example 1: Scale up on each `check_run` event](#example-1-scale-up-on-each-check_run-event)
|
|
||||||
- [Example 2: Scale on each `pull_request` event against `develop` or `main` branches](#example-2-scale-on-each-pull_request-event-against-develop-or-main-branches)
|
|
||||||
|
|
||||||
##### Example 1: Scale up on each `check_run` event
|
|
||||||
|
|
||||||
> Note: This should work almost like https://github.com/philips-labs/terraform-aws-github-runner
|
|
||||||
|
|
||||||
To scale up replicas of the runners for `example/myrepo` by 1 for 5 minutes on each `check_run`, you write manifests like the below:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
kind: RunnerDeployment
|
|
||||||
metadata:
|
|
||||||
name: myrunners
|
|
||||||
spec:
|
|
||||||
repository: example/myrepo
|
|
||||||
---
|
|
||||||
kind: HorizontalRunnerAutoscaler
|
|
||||||
spec:
|
|
||||||
scaleTargetRef:
|
|
||||||
name: myrunners
|
|
||||||
scaleUpTrigggers:
|
|
||||||
- githubEvent:
|
|
||||||
checkRun:
|
|
||||||
types: ["created"]
|
|
||||||
status: "queued"
|
|
||||||
amount: 1
|
|
||||||
duration: "5m"
|
|
||||||
```
|
|
||||||
|
|
||||||
###### Example 2: Scale on each `pull_request` event against `develop` or `main` branches
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
kind: RunnerDeployment:
|
|
||||||
metadata:
|
|
||||||
name: myrunners
|
|
||||||
spec:
|
|
||||||
repository: example/myrepo
|
|
||||||
---
|
|
||||||
kind: HorizontalRunnerAutoscaler
|
|
||||||
spec:
|
|
||||||
scaleTargetRef:
|
|
||||||
name: myrunners
|
|
||||||
scaleUpTrigggers:
|
|
||||||
- githubEvent:
|
|
||||||
pullRequest:
|
|
||||||
types: ["synchronize"]
|
|
||||||
branches: ["main", "develop"]
|
|
||||||
amount: 1
|
|
||||||
duration: "5m"
|
|
||||||
```
|
|
||||||
|
|
||||||
See ["activity types"](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request) for the list of valid values for `scaleUpTriggers[].githubEvent.pullRequest.types`.
|
|
||||||
|
|
||||||
### Runner with DinD
|
|
||||||
|
|
||||||
When using default runner, runner pod starts up 2 containers: runner and DinD (Docker-in-Docker). This might create issues if there's `LimitRange` set to namespace.
|
When using default runner, runner pod starts up 2 containers: runner and DinD (Docker-in-Docker). This might create issues if there's `LimitRange` set to namespace.
|
||||||
|
|
||||||
@@ -497,7 +311,7 @@ spec:
|
|||||||
|
|
||||||
This also helps with resources, as you don't need to give resources separately to docker and runner.
|
This also helps with resources, as you don't need to give resources separately to docker and runner.
|
||||||
|
|
||||||
### Additional tweaks
|
## Additional tweaks
|
||||||
|
|
||||||
You can pass details through the spec selector. Here's an eg. of what you may like to do:
|
You can pass details through the spec selector. Here's an eg. of what you may like to do:
|
||||||
|
|
||||||
@@ -529,14 +343,6 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
cpu: "2.0"
|
cpu: "2.0"
|
||||||
memory: "4Gi"
|
memory: "4Gi"
|
||||||
|
|
||||||
# Timeout after a node crashed or became unreachable to evict your pods somewhere else (default 5mins)
|
|
||||||
tolerations:
|
|
||||||
- key: "node.kubernetes.io/unreachable"
|
|
||||||
operator: "Exists"
|
|
||||||
effect: "NoExecute"
|
|
||||||
tolerationSeconds: 10
|
|
||||||
|
|
||||||
# If set to false, there are no privileged container and you cannot use docker.
|
# If set to false, there are no privileged container and you cannot use docker.
|
||||||
dockerEnabled: false
|
dockerEnabled: false
|
||||||
# If set to true, runner pod container only 1 container that's expected to be able to run docker, too.
|
# If set to true, runner pod container only 1 container that's expected to be able to run docker, too.
|
||||||
@@ -564,7 +370,7 @@ spec:
|
|||||||
workDir: /home/runner/work
|
workDir: /home/runner/work
|
||||||
```
|
```
|
||||||
|
|
||||||
### Runner labels
|
## Runner labels
|
||||||
|
|
||||||
To run a workflow job on a self-hosted runner, you can use the following syntax in your workflow:
|
To run a workflow job on a self-hosted runner, you can use the following syntax in your workflow:
|
||||||
|
|
||||||
@@ -601,7 +407,7 @@ jobs:
|
|||||||
|
|
||||||
Note that if you specify `self-hosted` in your workflow, then this will run your job on _any_ self-hosted runner, regardless of the labels that they have.
|
Note that if you specify `self-hosted` in your workflow, then this will run your job on _any_ self-hosted runner, regardless of the labels that they have.
|
||||||
|
|
||||||
### Runner Groups
|
## Runner Groups
|
||||||
|
|
||||||
Runner groups can be used to limit which repositories are able to use the GitHub Runner at an Organisation level. Runner groups have to be [created in GitHub first](https://docs.github.com/en/actions/hosting-your-own-runners/managing-access-to-self-hosted-runners-using-groups) before they can be referenced.
|
Runner groups can be used to limit which repositories are able to use the GitHub Runner at an Organisation level. Runner groups have to be [created in GitHub first](https://docs.github.com/en/actions/hosting-your-own-runners/managing-access-to-self-hosted-runners-using-groups) before they can be referenced.
|
||||||
|
|
||||||
@@ -620,7 +426,7 @@ spec:
|
|||||||
group: NewGroup
|
group: NewGroup
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using EKS IAM role for service accounts
|
## Using EKS IAM role for service accounts
|
||||||
|
|
||||||
`actions-runner-controller` v0.15.0 or later has support for EKS IAM role for service accounts.
|
`actions-runner-controller` v0.15.0 or later has support for EKS IAM role for service accounts.
|
||||||
|
|
||||||
@@ -646,7 +452,7 @@ spec:
|
|||||||
fsGroup: 1447
|
fsGroup: 1447
|
||||||
```
|
```
|
||||||
|
|
||||||
### Software installed in the runner image
|
## Software installed in the runner image
|
||||||
|
|
||||||
The GitHub hosted runners include a large amount of pre-installed software packages. For Ubuntu 18.04, this list can be found at <https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md>
|
The GitHub hosted runners include a large amount of pre-installed software packages. For Ubuntu 18.04, this list can be found at <https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md>
|
||||||
|
|
||||||
@@ -681,9 +487,9 @@ spec:
|
|||||||
image: YOUR_CUSTOM_DOCKER_IMAGE
|
image: YOUR_CUSTOM_DOCKER_IMAGE
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Errors
|
## Common Errors
|
||||||
|
|
||||||
#### invalid header field value
|
### invalid header field value
|
||||||
|
|
||||||
```json
|
```json
|
||||||
2020-11-12T22:17:30.693Z ERROR controller-runtime.controller Reconciler error {"controller": "runner", "request": "actions-runner-system/runner-deployment-dk7q8-dk5c9", "error": "failed to create registration token: Post \"https://api.github.com/orgs/$YOUR_ORG_HERE/actions/runners/registration-token\": net/http: invalid header field value \"Bearer $YOUR_TOKEN_HERE\\n\" for key Authorization"}
|
2020-11-12T22:17:30.693Z ERROR controller-runtime.controller Reconciler error {"controller": "runner", "request": "actions-runner-system/runner-deployment-dk7q8-dk5c9", "error": "failed to create registration token: Post \"https://api.github.com/orgs/$YOUR_ORG_HERE/actions/runners/registration-token\": net/http: invalid header field value \"Bearer $YOUR_TOKEN_HERE\\n\" for key Authorization"}
|
||||||
@@ -713,7 +519,7 @@ NAME=$DOCKER_USER/actions-runner-controller \
|
|||||||
|
|
||||||
Please follow the instructions explained in [Using Personal Access Token](#using-personal-access-token) to obtain
|
Please follow the instructions explained in [Using Personal Access Token](#using-personal-access-token) to obtain
|
||||||
`GITHUB_TOKEN`, and those in [Using GitHub App](#using-github-app) to obtain `APP_ID`, `INSTALLATION_ID`, and
|
`GITHUB_TOKEN`, and those in [Using GitHub App](#using-github-app) to obtain `APP_ID`, `INSTALLATION_ID`, and
|
||||||
`PRIAVTE_KEY_FILE_PATH`.
|
`PRIVATE_KEY_FILE_PATH`.
|
||||||
|
|
||||||
The test creates a one-off `kind` cluster, deploys `cert-manager` and `actions-runner-controller`,
|
The test creates a one-off `kind` cluster, deploys `cert-manager` and `actions-runner-controller`,
|
||||||
creates a `RunnerDeployment` custom resource for a public Git repository to confirm that the
|
creates a `RunnerDeployment` custom resource for a public Git repository to confirm that the
|
||||||
@@ -721,7 +527,7 @@ controller is able to bring up a runner pod with the actions runner registration
|
|||||||
|
|
||||||
If you prefer to test in a non-kind cluster, you can instead run:
|
If you prefer to test in a non-kind cluster, you can instead run:
|
||||||
|
|
||||||
```shell script
|
```shell
|
||||||
KUBECONFIG=path/to/kubeconfig \
|
KUBECONFIG=path/to/kubeconfig \
|
||||||
NAME=$DOCKER_USER/actions-runner-controller \
|
NAME=$DOCKER_USER/actions-runner-controller \
|
||||||
GITHUB_TOKEN=*** \
|
GITHUB_TOKEN=*** \
|
||||||
|
|||||||
@@ -41,56 +41,6 @@ type HorizontalRunnerAutoscalerSpec struct {
|
|||||||
// Metrics is the collection of various metric targets to calculate desired number of runners
|
// Metrics is the collection of various metric targets to calculate desired number of runners
|
||||||
// +optional
|
// +optional
|
||||||
Metrics []MetricSpec `json:"metrics,omitempty"`
|
Metrics []MetricSpec `json:"metrics,omitempty"`
|
||||||
|
|
||||||
// ScaleUpTriggers is an experimental feature to increase the desired replicas by 1
|
|
||||||
// on each webhook requested received by the webhookBasedAutoscaler.
|
|
||||||
//
|
|
||||||
// This feature requires you to also enable and deploy the webhookBasedAutoscaler onto your cluster.
|
|
||||||
//
|
|
||||||
// Note that the added runners remain until the next sync period at least,
|
|
||||||
// and they may or may not be used by GitHub Actions depending on the timing.
|
|
||||||
// They are intended to be used to gain "resource slack" immediately after you
|
|
||||||
// receive a webhook from GitHub, so that you can loosely expect MinReplicas runners to be always available.
|
|
||||||
ScaleUpTriggers []ScaleUpTrigger `json:"scaleUpTriggers,omitempty"`
|
|
||||||
|
|
||||||
CapacityReservations []CapacityReservation `json:"capacityReservations,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScaleUpTrigger struct {
|
|
||||||
GitHubEvent *GitHubEventScaleUpTriggerSpec `json:"githubEvent,omitempty"`
|
|
||||||
Amount int `json:"amount,omitempty"`
|
|
||||||
Duration metav1.Duration `json:"duration,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GitHubEventScaleUpTriggerSpec struct {
|
|
||||||
CheckRun *CheckRunSpec `json:"checkRun,omitempty"`
|
|
||||||
PullRequest *PullRequestSpec `json:"pullRequest,omitempty"`
|
|
||||||
Push *PushSpec `json:"push,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
|
||||||
type CheckRunSpec struct {
|
|
||||||
Types []string `json:"types,omitempty"`
|
|
||||||
Status string `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request
|
|
||||||
type PullRequestSpec struct {
|
|
||||||
Types []string `json:"types,omitempty"`
|
|
||||||
Branches []string `json:"branches,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushSpec is the condition for triggering scale-up on push event
|
|
||||||
// Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
|
||||||
type PushSpec struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// CapacityReservation specifies the number of replicas temporarily added
|
|
||||||
// to the scale target until ExpirationTime.
|
|
||||||
type CapacityReservation struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
ExpirationTime metav1.Time `json:"expirationTime,omitempty"`
|
|
||||||
Replicas int `json:"replicas,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScaleTargetRef struct {
|
type ScaleTargetRef struct {
|
||||||
@@ -141,17 +91,6 @@ type HorizontalRunnerAutoscalerStatus struct {
|
|||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
LastSuccessfulScaleOutTime *metav1.Time `json:"lastSuccessfulScaleOutTime,omitempty"`
|
LastSuccessfulScaleOutTime *metav1.Time `json:"lastSuccessfulScaleOutTime,omitempty"`
|
||||||
|
|
||||||
// +optional
|
|
||||||
CacheEntries []CacheEntry `json:"cacheEntries,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const CacheEntryKeyDesiredReplicas = "desiredReplicas"
|
|
||||||
|
|
||||||
type CacheEntry struct {
|
|
||||||
Key string `json:"key,omitempty"`
|
|
||||||
Value int `json:"value,omitempty"`
|
|
||||||
ExpirationTime metav1.Time `json:"expirationTime,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ import (
|
|||||||
|
|
||||||
// RunnerSpec defines the desired state of Runner
|
// RunnerSpec defines the desired state of Runner
|
||||||
type RunnerSpec struct {
|
type RunnerSpec struct {
|
||||||
// +optional
|
|
||||||
// +kubebuilder:validation:Pattern=`^[^/]+$`
|
|
||||||
Enterprise string `json:"enterprise,omitempty"`
|
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
// +kubebuilder:validation:Pattern=`^[^/]+$`
|
// +kubebuilder:validation:Pattern=`^[^/]+$`
|
||||||
Organization string `json:"organization,omitempty"`
|
Organization string `json:"organization,omitempty"`
|
||||||
@@ -96,22 +92,12 @@ type RunnerSpec struct {
|
|||||||
|
|
||||||
// ValidateRepository validates repository field.
|
// ValidateRepository validates repository field.
|
||||||
func (rs *RunnerSpec) ValidateRepository() error {
|
func (rs *RunnerSpec) ValidateRepository() error {
|
||||||
// Enterprise, Organization and repository are both exclusive.
|
// Organization and repository are both exclusive.
|
||||||
foundCount := 0
|
if len(rs.Organization) == 0 && len(rs.Repository) == 0 {
|
||||||
if len(rs.Organization) > 0 {
|
return errors.New("Spec needs organization or repository")
|
||||||
foundCount += 1
|
|
||||||
}
|
}
|
||||||
if len(rs.Repository) > 0 {
|
if len(rs.Organization) > 0 && len(rs.Repository) > 0 {
|
||||||
foundCount += 1
|
return errors.New("Spec cannot have both organization and repository")
|
||||||
}
|
|
||||||
if len(rs.Enterprise) > 0 {
|
|
||||||
foundCount += 1
|
|
||||||
}
|
|
||||||
if foundCount == 0 {
|
|
||||||
return errors.New("Spec needs enterprise, organization or repository")
|
|
||||||
}
|
|
||||||
if foundCount > 1 {
|
|
||||||
return errors.New("Spec cannot have many fields defined enterprise, organization and repository")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -127,7 +113,6 @@ type RunnerStatus struct {
|
|||||||
|
|
||||||
// RunnerStatusRegistration contains runner registration status
|
// RunnerStatusRegistration contains runner registration status
|
||||||
type RunnerStatusRegistration struct {
|
type RunnerStatusRegistration struct {
|
||||||
Enterprise string `json:"enterprise,omitempty"`
|
|
||||||
Organization string `json:"organization,omitempty"`
|
Organization string `json:"organization,omitempty"`
|
||||||
Repository string `json:"repository,omitempty"`
|
Repository string `json:"repository,omitempty"`
|
||||||
Labels []string `json:"labels,omitempty"`
|
Labels []string `json:"labels,omitempty"`
|
||||||
@@ -137,7 +122,6 @@ type RunnerStatusRegistration struct {
|
|||||||
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
// +kubebuilder:subresource:status
|
// +kubebuilder:subresource:status
|
||||||
// +kubebuilder:printcolumn:JSONPath=".spec.enterprise",name=Enterprise,type=string
|
|
||||||
// +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string
|
// +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string
|
||||||
// +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string
|
// +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string
|
||||||
// +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string
|
// +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string
|
||||||
|
|||||||
@@ -25,88 +25,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *CacheEntry) DeepCopyInto(out *CacheEntry) {
|
|
||||||
*out = *in
|
|
||||||
in.ExpirationTime.DeepCopyInto(&out.ExpirationTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CacheEntry.
|
|
||||||
func (in *CacheEntry) DeepCopy() *CacheEntry {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(CacheEntry)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *CapacityReservation) DeepCopyInto(out *CapacityReservation) {
|
|
||||||
*out = *in
|
|
||||||
in.ExpirationTime.DeepCopyInto(&out.ExpirationTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapacityReservation.
|
|
||||||
func (in *CapacityReservation) DeepCopy() *CapacityReservation {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(CapacityReservation)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *CheckRunSpec) DeepCopyInto(out *CheckRunSpec) {
|
|
||||||
*out = *in
|
|
||||||
if in.Types != nil {
|
|
||||||
in, out := &in.Types, &out.Types
|
|
||||||
*out = make([]string, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheckRunSpec.
|
|
||||||
func (in *CheckRunSpec) DeepCopy() *CheckRunSpec {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(CheckRunSpec)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *GitHubEventScaleUpTriggerSpec) DeepCopyInto(out *GitHubEventScaleUpTriggerSpec) {
|
|
||||||
*out = *in
|
|
||||||
if in.CheckRun != nil {
|
|
||||||
in, out := &in.CheckRun, &out.CheckRun
|
|
||||||
*out = new(CheckRunSpec)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
if in.PullRequest != nil {
|
|
||||||
in, out := &in.PullRequest, &out.PullRequest
|
|
||||||
*out = new(PullRequestSpec)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
if in.Push != nil {
|
|
||||||
in, out := &in.Push, &out.Push
|
|
||||||
*out = new(PushSpec)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubEventScaleUpTriggerSpec.
|
|
||||||
func (in *GitHubEventScaleUpTriggerSpec) DeepCopy() *GitHubEventScaleUpTriggerSpec {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(GitHubEventScaleUpTriggerSpec)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *HorizontalRunnerAutoscaler) DeepCopyInto(out *HorizontalRunnerAutoscaler) {
|
func (in *HorizontalRunnerAutoscaler) DeepCopyInto(out *HorizontalRunnerAutoscaler) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -192,20 +110,6 @@ func (in *HorizontalRunnerAutoscalerSpec) DeepCopyInto(out *HorizontalRunnerAuto
|
|||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in.ScaleUpTriggers != nil {
|
|
||||||
in, out := &in.ScaleUpTriggers, &out.ScaleUpTriggers
|
|
||||||
*out = make([]ScaleUpTrigger, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.CapacityReservations != nil {
|
|
||||||
in, out := &in.CapacityReservations, &out.CapacityReservations
|
|
||||||
*out = make([]CapacityReservation, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerSpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerSpec.
|
||||||
@@ -230,13 +134,6 @@ func (in *HorizontalRunnerAutoscalerStatus) DeepCopyInto(out *HorizontalRunnerAu
|
|||||||
in, out := &in.LastSuccessfulScaleOutTime, &out.LastSuccessfulScaleOutTime
|
in, out := &in.LastSuccessfulScaleOutTime, &out.LastSuccessfulScaleOutTime
|
||||||
*out = (*in).DeepCopy()
|
*out = (*in).DeepCopy()
|
||||||
}
|
}
|
||||||
if in.CacheEntries != nil {
|
|
||||||
in, out := &in.CacheEntries, &out.CacheEntries
|
|
||||||
*out = make([]CacheEntry, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerStatus.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerStatus.
|
||||||
@@ -269,46 +166,6 @@ func (in *MetricSpec) DeepCopy() *MetricSpec {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *PullRequestSpec) DeepCopyInto(out *PullRequestSpec) {
|
|
||||||
*out = *in
|
|
||||||
if in.Types != nil {
|
|
||||||
in, out := &in.Types, &out.Types
|
|
||||||
*out = make([]string, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
if in.Branches != nil {
|
|
||||||
in, out := &in.Branches, &out.Branches
|
|
||||||
*out = make([]string, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PullRequestSpec.
|
|
||||||
func (in *PullRequestSpec) DeepCopy() *PullRequestSpec {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(PullRequestSpec)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *PushSpec) DeepCopyInto(out *PushSpec) {
|
|
||||||
*out = *in
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSpec.
|
|
||||||
func (in *PushSpec) DeepCopy() *PushSpec {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(PushSpec)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Runner) DeepCopyInto(out *Runner) {
|
func (in *Runner) DeepCopyInto(out *Runner) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -758,24 +615,3 @@ func (in *ScaleTargetRef) DeepCopy() *ScaleTargetRef {
|
|||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *ScaleUpTrigger) DeepCopyInto(out *ScaleUpTrigger) {
|
|
||||||
*out = *in
|
|
||||||
if in.GitHubEvent != nil {
|
|
||||||
in, out := &in.GitHubEvent, &out.GitHubEvent
|
|
||||||
*out = new(GitHubEventScaleUpTriggerSpec)
|
|
||||||
(*in).DeepCopyInto(*out)
|
|
||||||
}
|
|
||||||
out.Duration = in.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScaleUpTrigger.
|
|
||||||
func (in *ScaleUpTrigger) DeepCopy() *ScaleUpTrigger {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(ScaleUpTrigger)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|||||||
4
artifacthub-repo.yml
Normal file
4
artifacthub-repo.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
repositoryID: 6e120248-b034-45e5-b16c-6015ecfa7c6c
|
||||||
|
owners:
|
||||||
|
- name: mumoshu
|
||||||
|
email: ykuoka@gmail.com
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow
|
|
||||||
lint-conf: charts/.ci/lint-config.yaml
|
|
||||||
chart-repos:
|
|
||||||
- jetstack=https://charts.jetstack.io
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
rules:
|
|
||||||
# One blank line is OK
|
|
||||||
empty-lines:
|
|
||||||
max-start: 1
|
|
||||||
max-end: 1
|
|
||||||
max: 1
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker run --rm -it -w /repo -v $(pwd):/repo quay.io/helmpack/chart-testing ct lint --all --config charts/.ci/ct-config.yaml
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
|
|
||||||
for chart in `ls charts`;
|
|
||||||
do
|
|
||||||
helm template --values charts/$chart/ci/ci-values.yaml charts/$chart | kube-score score - \
|
|
||||||
--ignore-test pod-networkpolicy \
|
|
||||||
--ignore-test deployment-has-poddisruptionbudget \
|
|
||||||
--ignore-test deployment-has-host-podantiaffinity \
|
|
||||||
--ignore-test pod-probes \
|
|
||||||
--ignore-test container-image-tag \
|
|
||||||
--enable-optional-test container-security-context-privileged \
|
|
||||||
--enable-optional-test container-security-context-readonlyrootfilesystem \
|
|
||||||
--ignore-test container-security-context
|
|
||||||
done
|
|
||||||
@@ -15,17 +15,9 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.4.0
|
version: 0.1.0
|
||||||
|
|
||||||
home: https://github.com/summerwind/actions-runner-controller
|
# This is the version number of the application being deployed. This version number should be
|
||||||
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
sources:
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
- https://github.com/summerwind/actions-runner-controller
|
appVersion: 0.11.2
|
||||||
|
|
||||||
maintainers:
|
|
||||||
- name: summerwind
|
|
||||||
email: contact@summerwind.jp
|
|
||||||
url: https://github.com/summerwind
|
|
||||||
- name: funkypenguin
|
|
||||||
email: davidy@funkypenguin.co.nz
|
|
||||||
url: https://www.funkypenguin.co.nz
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
# This file sets some opinionated values for kube-score to use
|
|
||||||
# when parsing the chart
|
|
||||||
image:
|
|
||||||
pullPolicy: Always
|
|
||||||
|
|
||||||
podSecurityContext:
|
|
||||||
fsGroup: 2000
|
|
||||||
|
|
||||||
securityContext:
|
|
||||||
capabilities:
|
|
||||||
drop:
|
|
||||||
- ALL
|
|
||||||
readOnlyRootFilesystem: true
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 2000
|
|
||||||
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
|
|
||||||
# Set the following to true to create a dummy secret, allowing the manager pod to start
|
|
||||||
# This is only useful in CI
|
|
||||||
createDummySecret: true
|
|
||||||
@@ -48,20 +48,6 @@ spec:
|
|||||||
description: HorizontalRunnerAutoscalerSpec defines the desired state of
|
description: HorizontalRunnerAutoscalerSpec defines the desired state of
|
||||||
HorizontalRunnerAutoscaler
|
HorizontalRunnerAutoscaler
|
||||||
properties:
|
properties:
|
||||||
capacityReservations:
|
|
||||||
items:
|
|
||||||
description: CapacityReservation specifies the number of replicas
|
|
||||||
temporarily added to the scale target until ExpirationTime.
|
|
||||||
properties:
|
|
||||||
expirationTime:
|
|
||||||
format: date-time
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
replicas:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
maxReplicas:
|
maxReplicas:
|
||||||
description: MinReplicas is the maximum number of replicas the deployment
|
description: MinReplicas is the maximum number of replicas the deployment
|
||||||
is allowed to scale
|
is allowed to scale
|
||||||
@@ -118,68 +104,9 @@ spec:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
scaleUpTriggers:
|
|
||||||
description: "ScaleUpTriggers is an experimental feature to increase
|
|
||||||
the desired replicas by 1 on each webhook requested received by the
|
|
||||||
webhookBasedAutoscaler. \n This feature requires you to also enable
|
|
||||||
and deploy the webhookBasedAutoscaler onto your cluster. \n Note that
|
|
||||||
the added runners remain until the next sync period at least, and
|
|
||||||
they may or may not be used by GitHub Actions depending on the timing.
|
|
||||||
They are intended to be used to gain \"resource slack\" immediately
|
|
||||||
after you receive a webhook from GitHub, so that you can loosely expect
|
|
||||||
MinReplicas runners to be always available."
|
|
||||||
items:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: integer
|
|
||||||
duration:
|
|
||||||
type: string
|
|
||||||
githubEvent:
|
|
||||||
properties:
|
|
||||||
checkRun:
|
|
||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
types:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
pullRequest:
|
|
||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request
|
|
||||||
properties:
|
|
||||||
branches:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
types:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
push:
|
|
||||||
description: PushSpec is the condition for triggering scale-up
|
|
||||||
on push event Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
properties:
|
properties:
|
||||||
cacheEntries:
|
|
||||||
items:
|
|
||||||
properties:
|
|
||||||
expirationTime:
|
|
||||||
format: date-time
|
|
||||||
type: string
|
|
||||||
key:
|
|
||||||
type: string
|
|
||||||
value:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
desiredReplicas:
|
desiredReplicas:
|
||||||
description: DesiredReplicas is the total number of desired, non-terminated
|
description: DesiredReplicas is the total number of desired, non-terminated
|
||||||
and latest pods to be set for the primary RunnerSet This doesn't include
|
and latest pods to be set for the primary RunnerSet This doesn't include
|
||||||
|
|||||||
@@ -426,9 +426,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
|
|||||||
@@ -426,9 +426,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ metadata:
|
|||||||
name: runners.actions.summerwind.dev
|
name: runners.actions.summerwind.dev
|
||||||
spec:
|
spec:
|
||||||
additionalPrinterColumns:
|
additionalPrinterColumns:
|
||||||
- JSONPath: .spec.enterprise
|
|
||||||
name: Enterprise
|
|
||||||
type: string
|
|
||||||
- JSONPath: .spec.organization
|
- JSONPath: .spec.organization
|
||||||
name: Organization
|
name: Organization
|
||||||
type: string
|
type: string
|
||||||
@@ -422,9 +419,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
@@ -1547,8 +1541,6 @@ spec:
|
|||||||
registration:
|
registration:
|
||||||
description: RunnerStatusRegistration contains runner registration status
|
description: RunnerStatusRegistration contains runner registration status
|
||||||
properties:
|
properties:
|
||||||
enterprise:
|
|
||||||
type: string
|
|
||||||
expiresAt:
|
expiresAt:
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
{{/*
|
|
||||||
Expand the name of the chart.
|
|
||||||
*/}}
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.name" -}}
|
|
||||||
{{- default .Chart.Name .Values.githubWebhookServer.nameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.instance" -}}
|
|
||||||
{{- printf "%s-%s" .Release.Name "github-webhook-server" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create a default fully qualified app name.
|
|
||||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
|
||||||
If release name contains chart name it will be used as a full name.
|
|
||||||
*/}}
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.fullname" -}}
|
|
||||||
{{- if .Values.githubWebhookServer.fullnameOverride }}
|
|
||||||
{{- .Values.githubWebhookServer.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- $name := default .Chart.Name .Values.githubWebhookServer.nameOverride }}
|
|
||||||
{{- $instance := include "actions-runner-controller-github-webhook-server.instance" . }}
|
|
||||||
{{- if contains $name $instance }}
|
|
||||||
{{- $instance | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- else }}
|
|
||||||
{{- printf "%s-%s-%s" .Release.Name $name "github-webhook-server" | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Selector labels
|
|
||||||
*/}}
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: {{ include "actions-runner-controller-github-webhook-server.name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ include "actions-runner-controller-github-webhook-server.instance" . }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create the name of the service account to use
|
|
||||||
*/}}
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.serviceAccountName" -}}
|
|
||||||
{{- if .Values.githubWebhookServer.serviceAccount.create }}
|
|
||||||
{{- default (include "actions-runner-controller-github-webhook-server.fullname" .) .Values.githubWebhookServer.serviceAccount.name }}
|
|
||||||
{{- else }}
|
|
||||||
{{- default "default" .Values.githubWebhookServer.serviceAccount.name }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{- define "actions-runner-controller-github-webhook-server.roleName" -}}
|
|
||||||
{{- include "actions-runner-controller-github-webhook-server.fullname" . }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -89,7 +89,7 @@ Create the name of the service account to use
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "actions-runner-controller.authProxyServiceName" -}}
|
{{- define "actions-runner-controller.authProxyServiceName" -}}
|
||||||
{{- include "actions-runner-controller.fullname" . }}-metrics-service
|
{{- include "actions-runner-controller.fullname" . }}-controller-manager-metrics-service
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "actions-runner-controller.selfsignedIssuerName" -}}
|
{{- define "actions-runner-controller.selfsignedIssuerName" -}}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
# This template only exists to facilitate CI testing of the chart, since
|
|
||||||
# a secret is expected to be found in the namespace by the controller manager
|
|
||||||
{{ if .Values.createDummySecret -}}
|
|
||||||
apiVersion: v1
|
|
||||||
data:
|
|
||||||
github_token: dGVzdA==
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: controller-manager
|
|
||||||
{{- end }}
|
|
||||||
@@ -57,10 +57,6 @@ spec:
|
|||||||
optional: true
|
optional: true
|
||||||
- name: GITHUB_APP_PRIVATE_KEY
|
- name: GITHUB_APP_PRIVATE_KEY
|
||||||
value: /etc/actions-runner-controller/github_app_private_key
|
value: /etc/actions-runner-controller/github_app_private_key
|
||||||
{{- range $key, $val := .Values.env }}
|
|
||||||
- name: {{ $key }}
|
|
||||||
value: {{ $val | quote }}
|
|
||||||
{{- end }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
|
||||||
name: manager
|
name: manager
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
@@ -70,14 +66,10 @@ spec:
|
|||||||
protocol: TCP
|
protocol: TCP
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: "/etc/actions-runner-controller"
|
- mountPath: "/etc/actions-runner-controller"
|
||||||
name: controller-manager
|
name: controller-manager
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- mountPath: /tmp
|
|
||||||
name: tmp
|
|
||||||
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||||
name: cert
|
name: cert
|
||||||
readOnly: true
|
readOnly: true
|
||||||
@@ -86,16 +78,11 @@ spec:
|
|||||||
- "--upstream=http://127.0.0.1:8080/"
|
- "--upstream=http://127.0.0.1:8080/"
|
||||||
- "--logtostderr=true"
|
- "--logtostderr=true"
|
||||||
- "--v=10"
|
- "--v=10"
|
||||||
image: "{{ .Values.kube_rbac_proxy.image.repository }}:{{ .Values.kube_rbac_proxy.image.tag }}"
|
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1
|
||||||
name: kube-rbac-proxy
|
name: kube-rbac-proxy
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8443
|
- containerPort: 8443
|
||||||
name: https
|
name: https
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
volumes:
|
volumes:
|
||||||
- name: controller-manager
|
- name: controller-manager
|
||||||
@@ -105,8 +92,6 @@ spec:
|
|||||||
secret:
|
secret:
|
||||||
defaultMode: 420
|
defaultMode: 420
|
||||||
secretName: webhook-server-cert
|
secretName: webhook-server-cert
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
{{- if .Values.githubWebhookServer.enabled }}
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "actions-runner-controller-github-webhook-server.fullname" . }}
|
|
||||||
namespace: {{ .Release.Namespace }}
|
|
||||||
labels:
|
|
||||||
{{- include "actions-runner-controller.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "actions-runner-controller-github-webhook-server.selectorLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
{{- with .Values.githubWebhookServer.podAnnotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "actions-runner-controller-github-webhook-server.selectorLabels" . | nindent 8 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.githubWebhookServer.imagePullSecrets }}
|
|
||||||
imagePullSecrets:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
serviceAccountName: {{ include "actions-runner-controller-github-webhook-server.serviceAccountName" . }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.githubWebhookServer.podSecurityContext | nindent 8 }}
|
|
||||||
{{- with .Values.githubWebhookServer.priorityClassName }}
|
|
||||||
priorityClassName: "{{ . }}"
|
|
||||||
{{- end }}
|
|
||||||
containers:
|
|
||||||
- args:
|
|
||||||
- "--metrics-addr=127.0.0.1:8080"
|
|
||||||
- "--enable-leader-election"
|
|
||||||
- "--sync-period={{ .Values.githubWebhookServer.syncPeriod }}"
|
|
||||||
command:
|
|
||||||
- "/github-webhook-server"
|
|
||||||
env:
|
|
||||||
- name: GITHUB_WEBHOOK_SECRET_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: github_webhook_secret_token
|
|
||||||
name: github-webhook-server
|
|
||||||
optional: true
|
|
||||||
{{- range $key, $val := .Values.githubWebhookServer.env }}
|
|
||||||
- name: {{ $key }}
|
|
||||||
value: {{ $val | quote }}
|
|
||||||
{{- end }}
|
|
||||||
image: "{{ .Values.githubWebhookServer.image.repository }}:{{ .Values.githubWebhookServer.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
|
|
||||||
name: github-webhook-server
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
ports:
|
|
||||||
- containerPort: 8000
|
|
||||||
name: github-webhook-server
|
|
||||||
protocol: TCP
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.githubWebhookServer.resources | nindent 12 }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.githubWebhookServer.securityContext | nindent 12 }}
|
|
||||||
- args:
|
|
||||||
- "--secure-listen-address=0.0.0.0:8443"
|
|
||||||
- "--upstream=http://127.0.0.1:8080/"
|
|
||||||
- "--logtostderr=true"
|
|
||||||
- "--v=10"
|
|
||||||
image: "{{ .Values.kube_rbac_proxy.image.repository }}:{{ .Values.kube_rbac_proxy.image.tag }}"
|
|
||||||
name: kube-rbac-proxy
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
ports:
|
|
||||||
- containerPort: 8443
|
|
||||||
name: https
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
|
||||||
terminationGracePeriodSeconds: 10
|
|
||||||
volumes:
|
|
||||||
- name: github-webhook-server
|
|
||||||
secret:
|
|
||||||
secretName: github-webhook-server
|
|
||||||
{{- with .Values.githubWebhookServer.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.githubWebhookServer.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.githubWebhookServer.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
{{- if .Values.githubWebhookServer.enabled }}
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
creationTimestamp: null
|
|
||||||
name: {{ include "actions-runner-controller-github-webhook-server.roleName" . }}
|
|
||||||
rules:
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- horizontalrunnerautoscalers
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- horizontalrunnerautoscalers/finalizers
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- horizontalrunnerautoscalers/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- runnerdeployments
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- runnerdeployments/finalizers
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
- watch
|
|
||||||
- apiGroups:
|
|
||||||
- actions.summerwind.dev
|
|
||||||
resources:
|
|
||||||
- runnerdeployments/status
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- patch
|
|
||||||
- update
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{{- if .Values.githubWebhookServer.enabled }}
|
|
||||||
{{- if .Values.githubWebhookServer.secret.enabled }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: github-webhook-server
|
|
||||||
namespace: {{ .Release.Namespace }}
|
|
||||||
labels:
|
|
||||||
{{- include "actions-runner-controller.labels" . | nindent 4 }}
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
{{- range $k, $v := .Values.githubWebhookServer.secret }}
|
|
||||||
{{ $k }}: {{ $v | toString | b64enc }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{{- if .Values.githubWebhookServer.enabled }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "actions-runner-controller-github-webhook-server.fullname" . }}
|
|
||||||
namespace: {{ .Release.Namespace }}
|
|
||||||
labels:
|
|
||||||
{{- include "actions-runner-controller.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.githubWebhookServer.service.type }}
|
|
||||||
ports:
|
|
||||||
{{ range $_, $port := .Values.githubWebhookServer.service.ports -}}
|
|
||||||
- {{ $port | toYaml | nindent 6 }}
|
|
||||||
{{- end }}
|
|
||||||
selector:
|
|
||||||
{{- include "actions-runner-controller-github-webhook-server.selectorLabels" . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{{- if .Values.githubWebhookServer.enabled -}}
|
|
||||||
{{- if .Values.githubWebhookServer.serviceAccount.create -}}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: {{ include "actions-runner-controller-github-webhook-server.serviceAccountName" . }}
|
|
||||||
namespace: {{ .Release.Namespace }}
|
|
||||||
labels:
|
|
||||||
{{- include "actions-runner-controller.labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.githubWebhookServer.serviceAccount.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -26,11 +26,6 @@ image:
|
|||||||
dindSidecarRepositoryAndTag: "docker:dind"
|
dindSidecarRepositoryAndTag: "docker:dind"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
kube_rbac_proxy:
|
|
||||||
image:
|
|
||||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
|
||||||
tag: v0.4.1
|
|
||||||
|
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
@@ -103,51 +98,3 @@ affinity: {}
|
|||||||
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
||||||
# PriorityClass: system-cluster-critical
|
# PriorityClass: system-cluster-critical
|
||||||
priorityClassName: ""
|
priorityClassName: ""
|
||||||
|
|
||||||
env: {}
|
|
||||||
# http_proxy: "proxy.com:8080"
|
|
||||||
# https_proxy: "proxy.com:8080"
|
|
||||||
# no_proxy: ""
|
|
||||||
|
|
||||||
githubWebhookServer:
|
|
||||||
enabled: false
|
|
||||||
labels: {}
|
|
||||||
replicaCount: 1
|
|
||||||
syncPeriod: 10m
|
|
||||||
secret:
|
|
||||||
enabled: false
|
|
||||||
### GitHub Webhook Configuration
|
|
||||||
#github_webhook_secret_token: ""
|
|
||||||
image:
|
|
||||||
repository: summerwind/actions-runner-controller
|
|
||||||
# Overrides the manager image tag whose default is the chart appVersion if the tag key is commented out
|
|
||||||
tag: "latest"
|
|
||||||
pullPolicy: IfNotPresent
|
|
||||||
imagePullSecrets: []
|
|
||||||
nameOverride: ""
|
|
||||||
fullnameOverride: ""
|
|
||||||
serviceAccount:
|
|
||||||
# Specifies whether a service account should be created
|
|
||||||
create: true
|
|
||||||
# Annotations to add to the service account
|
|
||||||
annotations: {}
|
|
||||||
# The name of the service account to use.
|
|
||||||
# If not set and create is true, a name is generated using the fullname template
|
|
||||||
name: ""
|
|
||||||
podAnnotations: {}
|
|
||||||
podSecurityContext: {}
|
|
||||||
# fsGroup: 2000
|
|
||||||
securityContext: {}
|
|
||||||
resources: {}
|
|
||||||
nodeSelector: {}
|
|
||||||
tolerations: []
|
|
||||||
affinity: {}
|
|
||||||
priorityClassName: ""
|
|
||||||
service:
|
|
||||||
type: NodePort
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8000
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
#nodePort: someFixedPortForUseWithTerraformCdkCfnEtc
|
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2021 The actions-runner-controller authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
"github.com/summerwind/actions-runner-controller/controllers"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/exec"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
|
||||||
// +kubebuilder:scaffold:imports
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
scheme = runtime.NewScheme()
|
|
||||||
setupLog = ctrl.Log.WithName("setup")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_ = clientgoscheme.AddToScheme(scheme)
|
|
||||||
|
|
||||||
_ = actionsv1alpha1.AddToScheme(scheme)
|
|
||||||
// +kubebuilder:scaffold:scheme
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
|
|
||||||
webhookAddr string
|
|
||||||
metricsAddr string
|
|
||||||
|
|
||||||
// The secret token of the GitHub Webhook. See https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks
|
|
||||||
webhookSecretToken string
|
|
||||||
|
|
||||||
watchNamespace string
|
|
||||||
|
|
||||||
enableLeaderElection bool
|
|
||||||
syncPeriod time.Duration
|
|
||||||
)
|
|
||||||
|
|
||||||
webhookSecretToken = os.Getenv("GITHUB_WEBHOOK_SECRET_TOKEN")
|
|
||||||
|
|
||||||
flag.StringVar(&webhookAddr, "webhook-addr", ":8000", "The address the metric endpoint binds to.")
|
|
||||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
|
||||||
flag.StringVar(&watchNamespace, "watch-namespace", "", "The namespace to watch for HorizontalRunnerAutoscaler's to scale on Webhook. Set to empty for letting it watch for all namespaces.")
|
|
||||||
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
|
||||||
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
|
|
||||||
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled. When you use autoscaling, set to a lower value like 10 minute, because this corresponds to the minimum time to react on demand change")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if webhookSecretToken == "" {
|
|
||||||
setupLog.Info("-webhook-secret-token is missing or empty. Create one following https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks")
|
|
||||||
}
|
|
||||||
|
|
||||||
if watchNamespace == "" {
|
|
||||||
setupLog.Info("-watch-namespace is empty. HorizontalRunnerAutoscalers in all the namespaces are watched, cached, and considered as scale targets.")
|
|
||||||
} else {
|
|
||||||
setupLog.Info("-watch-namespace is %q. Only HorizontalRunnerAutoscalers in %q are watched, cached, and considered as scale targets.")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := zap.New(func(o *zap.Options) {
|
|
||||||
o.Development = true
|
|
||||||
})
|
|
||||||
|
|
||||||
ctrl.SetLogger(logger)
|
|
||||||
|
|
||||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
|
||||||
Scheme: scheme,
|
|
||||||
SyncPeriod: &syncPeriod,
|
|
||||||
LeaderElection: enableLeaderElection,
|
|
||||||
Namespace: watchNamespace,
|
|
||||||
MetricsBindAddress: metricsAddr,
|
|
||||||
Port: 9443,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
setupLog.Error(err, "unable to start manager")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
hraGitHubWebhook := &controllers.HorizontalRunnerAutoscalerGitHubWebhook{
|
|
||||||
Client: mgr.GetClient(),
|
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("Runner"),
|
|
||||||
Recorder: nil,
|
|
||||||
Scheme: mgr.GetScheme(),
|
|
||||||
SecretKeyBytes: []byte(webhookSecretToken),
|
|
||||||
WatchNamespace: watchNamespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = hraGitHubWebhook.SetupWithManager(mgr); err != nil {
|
|
||||||
setupLog.Error(err, "unable to create controller", "controller", "Runner")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer cancel()
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
setupLog.Info("starting webhook server")
|
|
||||||
if err := mgr.Start(ctx.Done()); err != nil {
|
|
||||||
setupLog.Error(err, "problem running manager")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", hraGitHubWebhook.Handle)
|
|
||||||
|
|
||||||
srv := http.Server{
|
|
||||||
Addr: webhookAddr,
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer cancel()
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
|
|
||||||
srv.Shutdown(context.Background())
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
setupLog.Error(err, "problem running http server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-ctrl.SetupSignalHandler()
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
@@ -48,20 +48,6 @@ spec:
|
|||||||
description: HorizontalRunnerAutoscalerSpec defines the desired state of
|
description: HorizontalRunnerAutoscalerSpec defines the desired state of
|
||||||
HorizontalRunnerAutoscaler
|
HorizontalRunnerAutoscaler
|
||||||
properties:
|
properties:
|
||||||
capacityReservations:
|
|
||||||
items:
|
|
||||||
description: CapacityReservation specifies the number of replicas
|
|
||||||
temporarily added to the scale target until ExpirationTime.
|
|
||||||
properties:
|
|
||||||
expirationTime:
|
|
||||||
format: date-time
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
replicas:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
maxReplicas:
|
maxReplicas:
|
||||||
description: MinReplicas is the maximum number of replicas the deployment
|
description: MinReplicas is the maximum number of replicas the deployment
|
||||||
is allowed to scale
|
is allowed to scale
|
||||||
@@ -118,68 +104,9 @@ spec:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
scaleUpTriggers:
|
|
||||||
description: "ScaleUpTriggers is an experimental feature to increase
|
|
||||||
the desired replicas by 1 on each webhook requested received by the
|
|
||||||
webhookBasedAutoscaler. \n This feature requires you to also enable
|
|
||||||
and deploy the webhookBasedAutoscaler onto your cluster. \n Note that
|
|
||||||
the added runners remain until the next sync period at least, and
|
|
||||||
they may or may not be used by GitHub Actions depending on the timing.
|
|
||||||
They are intended to be used to gain \"resource slack\" immediately
|
|
||||||
after you receive a webhook from GitHub, so that you can loosely expect
|
|
||||||
MinReplicas runners to be always available."
|
|
||||||
items:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: integer
|
|
||||||
duration:
|
|
||||||
type: string
|
|
||||||
githubEvent:
|
|
||||||
properties:
|
|
||||||
checkRun:
|
|
||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#check_run
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
types:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
pullRequest:
|
|
||||||
description: https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request
|
|
||||||
properties:
|
|
||||||
branches:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
types:
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
|
||||||
push:
|
|
||||||
description: PushSpec is the condition for triggering scale-up
|
|
||||||
on push event Also see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
type: object
|
type: object
|
||||||
status:
|
status:
|
||||||
properties:
|
properties:
|
||||||
cacheEntries:
|
|
||||||
items:
|
|
||||||
properties:
|
|
||||||
expirationTime:
|
|
||||||
format: date-time
|
|
||||||
type: string
|
|
||||||
key:
|
|
||||||
type: string
|
|
||||||
value:
|
|
||||||
type: integer
|
|
||||||
type: object
|
|
||||||
type: array
|
|
||||||
desiredReplicas:
|
desiredReplicas:
|
||||||
description: DesiredReplicas is the total number of desired, non-terminated
|
description: DesiredReplicas is the total number of desired, non-terminated
|
||||||
and latest pods to be set for the primary RunnerSet This doesn't include
|
and latest pods to be set for the primary RunnerSet This doesn't include
|
||||||
|
|||||||
@@ -426,9 +426,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
|
|||||||
@@ -426,9 +426,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ metadata:
|
|||||||
name: runners.actions.summerwind.dev
|
name: runners.actions.summerwind.dev
|
||||||
spec:
|
spec:
|
||||||
additionalPrinterColumns:
|
additionalPrinterColumns:
|
||||||
- JSONPath: .spec.enterprise
|
|
||||||
name: Enterprise
|
|
||||||
type: string
|
|
||||||
- JSONPath: .spec.organization
|
- JSONPath: .spec.organization
|
||||||
name: Organization
|
name: Organization
|
||||||
type: string
|
type: string
|
||||||
@@ -422,9 +419,6 @@ spec:
|
|||||||
type: object
|
type: object
|
||||||
dockerdWithinRunnerContainer:
|
dockerdWithinRunnerContainer:
|
||||||
type: boolean
|
type: boolean
|
||||||
enterprise:
|
|
||||||
pattern: ^[^/]+$
|
|
||||||
type: string
|
|
||||||
env:
|
env:
|
||||||
items:
|
items:
|
||||||
description: EnvVar represents an environment variable present in a Container.
|
description: EnvVar represents an environment variable present in a Container.
|
||||||
@@ -1547,8 +1541,6 @@ spec:
|
|||||||
registration:
|
registration:
|
||||||
description: RunnerStatusRegistration contains runner registration status
|
description: RunnerStatusRegistration contains runner registration status
|
||||||
properties:
|
properties:
|
||||||
enterprise:
|
|
||||||
type: string
|
|
||||||
expiresAt:
|
expiresAt:
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
@@ -20,47 +19,6 @@ const (
|
|||||||
defaultScaleDownFactor = 0.7
|
defaultScaleDownFactor = 0.7
|
||||||
)
|
)
|
||||||
|
|
||||||
func getValueAvailableAt(now time.Time, from, to *time.Time, reservedValue int) *int {
|
|
||||||
if to != nil && now.After(*to) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if from != nil && now.Before(*from) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &reservedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HorizontalRunnerAutoscalerReconciler) getDesiredReplicasFromCache(hra v1alpha1.HorizontalRunnerAutoscaler) *int {
|
|
||||||
var entry *v1alpha1.CacheEntry
|
|
||||||
|
|
||||||
for i := range hra.Status.CacheEntries {
|
|
||||||
ent := hra.Status.CacheEntries[i]
|
|
||||||
|
|
||||||
if ent.Key != v1alpha1.CacheEntryKeyDesiredReplicas {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !time.Now().Before(ent.ExpirationTime.Time) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
entry = &ent
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry != nil {
|
|
||||||
v := getValueAvailableAt(time.Now(), nil, &entry.ExpirationTime.Time, entry.Value)
|
|
||||||
if v != nil {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *HorizontalRunnerAutoscalerReconciler) determineDesiredReplicas(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
|
func (r *HorizontalRunnerAutoscalerReconciler) determineDesiredReplicas(rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
|
||||||
if hra.Spec.MinReplicas == nil {
|
if hra.Spec.MinReplicas == nil {
|
||||||
return nil, fmt.Errorf("horizontalrunnerautoscaler %s/%s is missing minReplicas", hra.Namespace, hra.Name)
|
return nil, fmt.Errorf("horizontalrunnerautoscaler %s/%s is missing minReplicas", hra.Namespace, hra.Name)
|
||||||
@@ -138,12 +96,12 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByQueuedAndInPro
|
|||||||
|
|
||||||
for _, repo := range repos {
|
for _, repo := range repos {
|
||||||
user, repoName := repo[0], repo[1]
|
user, repoName := repo[0], repo[1]
|
||||||
workflowRuns, err := r.GitHubClient.ListRepositoryWorkflowRuns(context.TODO(), user, repoName)
|
list, _, err := r.GitHubClient.Actions.ListRepositoryWorkflowRuns(context.TODO(), user, repoName, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, run := range workflowRuns {
|
for _, run := range list.WorkflowRuns {
|
||||||
total++
|
total++
|
||||||
|
|
||||||
// In May 2020, there are only 3 statuses.
|
// In May 2020, there are only 3 statuses.
|
||||||
@@ -246,7 +204,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListRunners will return all runners managed by GitHub - not restricted to ns
|
// ListRunners will return all runners managed by GitHub - not restricted to ns
|
||||||
runners, err := r.GitHubClient.ListRunners(ctx, "", orgName, "")
|
runners, err := r.GitHubClient.ListRunners(ctx, orgName, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,11 +157,7 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
|
|||||||
_ = v1alpha1.AddToScheme(scheme)
|
_ = v1alpha1.AddToScheme(scheme)
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||||
server := fake.NewServer(
|
server := fake.NewServer(fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns), fake.WithListWorkflowJobsResponse(200, tc.workflowJobs))
|
||||||
fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns),
|
|
||||||
fake.WithListWorkflowJobsResponse(200, tc.workflowJobs),
|
|
||||||
fake.WithListRunnersResponse(200, fake.RunnersListBody),
|
|
||||||
)
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := newGithubClient(server)
|
client := newGithubClient(server)
|
||||||
|
|
||||||
@@ -372,11 +368,7 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
|
|||||||
_ = v1alpha1.AddToScheme(scheme)
|
_ = v1alpha1.AddToScheme(scheme)
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||||
server := fake.NewServer(
|
server := fake.NewServer(fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns), fake.WithListWorkflowJobsResponse(200, tc.workflowJobs))
|
||||||
fake.WithListRepositoryWorkflowRunsResponse(200, tc.workflowRuns),
|
|
||||||
fake.WithListWorkflowJobsResponse(200, tc.workflowJobs),
|
|
||||||
fake.WithListRunnersResponse(200, fake.RunnersListBody),
|
|
||||||
)
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
client := newGithubClient(server)
|
client := newGithubClient(server)
|
||||||
|
|
||||||
|
|||||||
@@ -1,375 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2020 The actions-runner-controller authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"net/http"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
gogithub "github.com/google/go-github/v33/github"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/client-go/tools/record"
|
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
|
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
scaleTargetKey = "scaleTarget"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HorizontalRunnerAutoscalerGitHubWebhook autoscales a HorizontalRunnerAutoscaler and the RunnerDeployment on each
|
|
||||||
// GitHub Webhook received
|
|
||||||
type HorizontalRunnerAutoscalerGitHubWebhook struct {
|
|
||||||
client.Client
|
|
||||||
Log logr.Logger
|
|
||||||
Recorder record.EventRecorder
|
|
||||||
Scheme *runtime.Scheme
|
|
||||||
|
|
||||||
// SecretKeyBytes is the byte representation of the Webhook secret token
|
|
||||||
// the administrator is generated and specified in GitHub Web UI.
|
|
||||||
SecretKeyBytes []byte
|
|
||||||
|
|
||||||
// WatchNamespace is the namespace to watch for HorizontalRunnerAutoscaler's to be
|
|
||||||
// scaled on Webhook.
|
|
||||||
// Set to empty for letting it watch for all namespaces.
|
|
||||||
WatchNamespace string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers,verbs=get;list;watch;create;update;patch;delete
|
|
||||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers/finalizers,verbs=get;list;watch;create;update;patch;delete
|
|
||||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=horizontalrunnerautoscalers/status,verbs=get;update;patch
|
|
||||||
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var (
|
|
||||||
ok bool
|
|
||||||
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if !ok {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
msg := err.Error()
|
|
||||||
if written, err := w.Write([]byte(msg)); err != nil {
|
|
||||||
autoscaler.Log.Error(err, "failed writing http error response", "msg", msg, "written", written)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if r.Body != nil {
|
|
||||||
r.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var payload []byte
|
|
||||||
|
|
||||||
if len(autoscaler.SecretKeyBytes) > 0 {
|
|
||||||
payload, err = gogithub.ValidatePayload(r, autoscaler.SecretKeyBytes)
|
|
||||||
if err != nil {
|
|
||||||
autoscaler.Log.Error(err, "error validating request body")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
payload, err = ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
autoscaler.Log.Error(err, "error reading request body")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webhookType := gogithub.WebHookType(r)
|
|
||||||
event, err := gogithub.ParseWebHook(webhookType, payload)
|
|
||||||
if err != nil {
|
|
||||||
var s string
|
|
||||||
if payload != nil {
|
|
||||||
s = string(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
autoscaler.Log.Error(err, "could not parse webhook", "webhookType", webhookType, "payload", s)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var target *ScaleTarget
|
|
||||||
|
|
||||||
autoscaler.Log.Info("processing webhook event", "eventType", webhookType)
|
|
||||||
|
|
||||||
switch e := event.(type) {
|
|
||||||
case *gogithub.PushEvent:
|
|
||||||
target, err = autoscaler.getScaleUpTarget(
|
|
||||||
context.TODO(),
|
|
||||||
*e.Repo.Name,
|
|
||||||
*e.Repo.Organization,
|
|
||||||
autoscaler.MatchPushEvent(e),
|
|
||||||
)
|
|
||||||
case *gogithub.PullRequestEvent:
|
|
||||||
target, err = autoscaler.getScaleUpTarget(
|
|
||||||
context.TODO(),
|
|
||||||
*e.Repo.Name,
|
|
||||||
*e.Repo.Organization.Name,
|
|
||||||
autoscaler.MatchPullRequestEvent(e),
|
|
||||||
)
|
|
||||||
case *gogithub.CheckRunEvent:
|
|
||||||
target, err = autoscaler.getScaleUpTarget(
|
|
||||||
context.TODO(),
|
|
||||||
*e.Repo.Name,
|
|
||||||
*e.Org.Name,
|
|
||||||
autoscaler.MatchCheckRunEvent(e),
|
|
||||||
)
|
|
||||||
case *gogithub.PingEvent:
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
msg := "pong"
|
|
||||||
|
|
||||||
if written, err := w.Write([]byte(msg)); err != nil {
|
|
||||||
autoscaler.Log.Error(err, "failed writing http response", "msg", msg, "written", written)
|
|
||||||
}
|
|
||||||
|
|
||||||
autoscaler.Log.Info("received ping event")
|
|
||||||
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
autoscaler.Log.Info("unknown event type", "eventType", webhookType)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
autoscaler.Log.Error(err, "handling check_run event")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if target == nil {
|
|
||||||
msg := "no horizontalrunnerautoscaler to scale for this github event"
|
|
||||||
|
|
||||||
autoscaler.Log.Info(msg, "eventType", webhookType)
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
if written, err := w.Write([]byte(msg)); err != nil {
|
|
||||||
autoscaler.Log.Error(err, "failed writing http response", "msg", msg, "written", written)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := autoscaler.tryScaleUp(context.TODO(), target); err != nil {
|
|
||||||
autoscaler.Log.Error(err, "could not scale up")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("scaled %s by 1", target.Name)
|
|
||||||
|
|
||||||
autoscaler.Log.Info(msg)
|
|
||||||
|
|
||||||
if written, err := w.Write([]byte(msg)); err != nil {
|
|
||||||
autoscaler.Log.Error(err, "failed writing http response", "msg", msg, "written", written)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) findHRAsByKey(ctx context.Context, value string) ([]v1alpha1.HorizontalRunnerAutoscaler, error) {
|
|
||||||
ns := autoscaler.WatchNamespace
|
|
||||||
|
|
||||||
var defaultListOpts []client.ListOption
|
|
||||||
|
|
||||||
if ns != "" {
|
|
||||||
defaultListOpts = append(defaultListOpts, client.InNamespace(ns))
|
|
||||||
}
|
|
||||||
|
|
||||||
var hras []v1alpha1.HorizontalRunnerAutoscaler
|
|
||||||
|
|
||||||
if value != "" {
|
|
||||||
opts := append([]client.ListOption{}, defaultListOpts...)
|
|
||||||
opts = append(opts, client.MatchingFields{scaleTargetKey: value})
|
|
||||||
|
|
||||||
var hraList v1alpha1.HorizontalRunnerAutoscalerList
|
|
||||||
|
|
||||||
if err := autoscaler.List(ctx, &hraList, opts...); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range hraList.Items {
|
|
||||||
hras = append(hras, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hras, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTriggerConditionAgainstEvent(types []string, eventAction *string) bool {
|
|
||||||
if len(types) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if eventAction == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tpe := range types {
|
|
||||||
if tpe == *eventAction {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScaleTarget struct {
|
|
||||||
v1alpha1.HorizontalRunnerAutoscaler
|
|
||||||
v1alpha1.ScaleUpTrigger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) searchScaleTargets(hras []v1alpha1.HorizontalRunnerAutoscaler, f func(v1alpha1.ScaleUpTrigger) bool) []ScaleTarget {
|
|
||||||
var matched []ScaleTarget
|
|
||||||
|
|
||||||
for _, hra := range hras {
|
|
||||||
if !hra.ObjectMeta.DeletionTimestamp.IsZero() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, scaleUpTrigger := range hra.Spec.ScaleUpTriggers {
|
|
||||||
if !f(scaleUpTrigger) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matched = append(matched, ScaleTarget{
|
|
||||||
HorizontalRunnerAutoscaler: hra,
|
|
||||||
ScaleUpTrigger: scaleUpTrigger,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleTarget(ctx context.Context, name string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) {
|
|
||||||
hras, err := autoscaler.findHRAsByKey(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
targets := autoscaler.searchScaleTargets(hras, f)
|
|
||||||
|
|
||||||
if len(targets) != 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &targets[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTarget(ctx context.Context, repoNameFromWebhook, orgNameFromWebhook string, f func(v1alpha1.ScaleUpTrigger) bool) (*ScaleTarget, error) {
|
|
||||||
if target, err := autoscaler.getScaleTarget(ctx, repoNameFromWebhook, f); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if target != nil {
|
|
||||||
autoscaler.Log.Info("scale up target is repository-wide runners", "repository", repoNameFromWebhook)
|
|
||||||
return target, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if target, err := autoscaler.getScaleTarget(ctx, orgNameFromWebhook, f); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if target != nil {
|
|
||||||
autoscaler.Log.Info("scale up target is organizational runners", "repository", orgNameFromWebhook)
|
|
||||||
return target, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) tryScaleUp(ctx context.Context, target *ScaleTarget) error {
|
|
||||||
if target == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log := autoscaler.Log.WithValues("horizontalrunnerautoscaler", target.HorizontalRunnerAutoscaler.Name)
|
|
||||||
|
|
||||||
copy := target.HorizontalRunnerAutoscaler.DeepCopy()
|
|
||||||
|
|
||||||
amount := 1
|
|
||||||
|
|
||||||
if target.ScaleUpTrigger.Amount > 0 {
|
|
||||||
amount = target.ScaleUpTrigger.Amount
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.Spec.CapacityReservations = append(copy.Spec.CapacityReservations, v1alpha1.CapacityReservation{
|
|
||||||
ExpirationTime: metav1.Time{Time: time.Now().Add(target.ScaleUpTrigger.Duration.Duration)},
|
|
||||||
Replicas: amount,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := autoscaler.Client.Update(ctx, copy); err != nil {
|
|
||||||
log.Error(err, "Failed to update horizontalrunnerautoscaler resource")
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) SetupWithManager(mgr ctrl.Manager) error {
|
|
||||||
autoscaler.Recorder = mgr.GetEventRecorderFor("webhookbasedautoscaler")
|
|
||||||
|
|
||||||
if err := mgr.GetFieldIndexer().IndexField(&v1alpha1.HorizontalRunnerAutoscaler{}, scaleTargetKey, func(rawObj runtime.Object) []string {
|
|
||||||
hra := rawObj.(*v1alpha1.HorizontalRunnerAutoscaler)
|
|
||||||
|
|
||||||
if hra.Spec.ScaleTargetRef.Name == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rd v1alpha1.RunnerDeployment
|
|
||||||
|
|
||||||
if err := autoscaler.Client.Get(context.Background(), types.NamespacedName{Namespace: hra.Namespace, Name: hra.Spec.ScaleTargetRef.Name}, &rd); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{rd.Spec.Template.Spec.Repository, rd.Spec.Template.Spec.Organization}
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
|
||||||
For(&v1alpha1.HorizontalRunnerAutoscaler{}).
|
|
||||||
Complete(autoscaler)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(event *github.CheckRunEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
g := scaleUpTrigger.GitHubEvent
|
|
||||||
|
|
||||||
if g == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cr := g.CheckRun
|
|
||||||
|
|
||||||
if cr == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matchTriggerConditionAgainstEvent(cr.Types, event.Action) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if cr.Status != "" && (event.CheckRun == nil || event.CheckRun.Status == nil || *event.CheckRun.Status != cr.Status) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPullRequestEvent(event *github.PullRequestEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
g := scaleUpTrigger.GitHubEvent
|
|
||||||
|
|
||||||
if g == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
pr := g.PullRequest
|
|
||||||
|
|
||||||
if pr == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matchTriggerConditionAgainstEvent(pr.Types, event.Action) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matchTriggerConditionAgainstEvent(pr.Branches, event.PullRequest.Base.Ref) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPushEvent(event *github.PushEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
return func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
|
||||||
g := scaleUpTrigger.GitHubEvent
|
|
||||||
|
|
||||||
if g == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
push := g.Push
|
|
||||||
|
|
||||||
if push == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
actionsv1alpha1 "github.com/summerwind/actions-runner-controller/api/v1alpha1"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sc = runtime.NewScheme()
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_ = clientgoscheme.AddToScheme(sc)
|
|
||||||
_ = actionsv1alpha1.AddToScheme(sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebhookCheckRun(t *testing.T) {
|
|
||||||
testServer(t,
|
|
||||||
"check_run",
|
|
||||||
&github.CheckRunEvent{
|
|
||||||
CheckRun: &github.CheckRun{
|
|
||||||
Status: github.String("queued"),
|
|
||||||
},
|
|
||||||
Repo: &github.Repository{
|
|
||||||
Name: github.String("myorg/myrepo"),
|
|
||||||
},
|
|
||||||
Org: &github.Organization{
|
|
||||||
Name: github.String("myorg"),
|
|
||||||
},
|
|
||||||
Action: github.String("created"),
|
|
||||||
},
|
|
||||||
200,
|
|
||||||
"no horizontalrunnerautoscaler to scale for this github event",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebhookPullRequest(t *testing.T) {
|
|
||||||
testServer(t,
|
|
||||||
"pull_request",
|
|
||||||
&github.PullRequestEvent{
|
|
||||||
PullRequest: &github.PullRequest{
|
|
||||||
Base: &github.PullRequestBranch{
|
|
||||||
Ref: github.String("main"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Repo: &github.Repository{
|
|
||||||
Name: github.String("myorg/myrepo"),
|
|
||||||
Organization: &github.Organization{
|
|
||||||
Name: github.String("myorg"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: github.String("created"),
|
|
||||||
},
|
|
||||||
200,
|
|
||||||
"no horizontalrunnerautoscaler to scale for this github event",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebhookPush(t *testing.T) {
|
|
||||||
testServer(t,
|
|
||||||
"push",
|
|
||||||
&github.PushEvent{
|
|
||||||
Repo: &github.PushEventRepository{
|
|
||||||
Name: github.String("myrepo"),
|
|
||||||
Organization: github.String("myorg"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
200,
|
|
||||||
"no horizontalrunnerautoscaler to scale for this github event",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebhookPing(t *testing.T) {
|
|
||||||
testServer(t,
|
|
||||||
"ping",
|
|
||||||
&github.PingEvent{
|
|
||||||
Zen: github.String("zen"),
|
|
||||||
},
|
|
||||||
200,
|
|
||||||
"pong",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func installTestLogger(webhook *HorizontalRunnerAutoscalerGitHubWebhook) *bytes.Buffer {
|
|
||||||
logs := &bytes.Buffer{}
|
|
||||||
|
|
||||||
log := testLogger{
|
|
||||||
name: "testlog",
|
|
||||||
writer: logs,
|
|
||||||
}
|
|
||||||
|
|
||||||
webhook.Log = &log
|
|
||||||
|
|
||||||
return logs
|
|
||||||
}
|
|
||||||
|
|
||||||
func testServer(t *testing.T, eventType string, event interface{}, wantCode int, wantBody string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
hraWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{}
|
|
||||||
|
|
||||||
var initObjs []runtime.Object
|
|
||||||
|
|
||||||
client := fake.NewFakeClientWithScheme(sc, initObjs...)
|
|
||||||
|
|
||||||
logs := installTestLogger(hraWebhook)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if t.Failed() {
|
|
||||||
t.Logf("diagnostics: %s", logs.String())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
hraWebhook.Client = client
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", hraWebhook.Handle)
|
|
||||||
|
|
||||||
server := httptest.NewServer(mux)
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
resp, err := sendWebhook(server, eventType, event)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if resp.StatusCode != wantCode {
|
|
||||||
t.Error("status:", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
respBody, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(respBody) != wantBody {
|
|
||||||
t.Fatal("body:", string(respBody))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendWebhook(server *httptest.Server, eventType string, event interface{}) (*http.Response, error) {
|
|
||||||
jsonBuf := &bytes.Buffer{}
|
|
||||||
enc := json.NewEncoder(jsonBuf)
|
|
||||||
enc.SetIndent(" ", "")
|
|
||||||
err := enc.Encode(event)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("[bug in test] encoding event to json: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBody := jsonBuf.Bytes()
|
|
||||||
|
|
||||||
u, err := url.Parse(server.URL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing server url: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: u,
|
|
||||||
Header: map[string][]string{
|
|
||||||
"X-GitHub-Event": {eventType},
|
|
||||||
"Content-Type": {"application/json"},
|
|
||||||
},
|
|
||||||
Body: ioutil.NopCloser(bytes.NewBuffer(reqBody)),
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.DefaultClient.Do(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testLogger is a sample logr.Logger that logs in-memory.
|
|
||||||
// It's only for testing log outputs.
|
|
||||||
type testLogger struct {
|
|
||||||
name string
|
|
||||||
keyValues map[string]interface{}
|
|
||||||
|
|
||||||
writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ logr.Logger = &testLogger{}
|
|
||||||
|
|
||||||
func (l *testLogger) Info(msg string, kvs ...interface{}) {
|
|
||||||
fmt.Fprintf(l.writer, "%s] %s\t", l.name, msg)
|
|
||||||
for k, v := range l.keyValues {
|
|
||||||
fmt.Fprintf(l.writer, "%s=%+v ", k, v)
|
|
||||||
}
|
|
||||||
for i := 0; i < len(kvs); i += 2 {
|
|
||||||
fmt.Fprintf(l.writer, "%s=%+v ", kvs[i], kvs[i+1])
|
|
||||||
}
|
|
||||||
fmt.Fprintf(l.writer, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *testLogger) Enabled() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testLogger) Error(err error, msg string, kvs ...interface{}) {
|
|
||||||
kvs = append(kvs, "error", err)
|
|
||||||
l.Info(msg, kvs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testLogger) V(_ int) logr.InfoLogger {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testLogger) WithName(name string) logr.Logger {
|
|
||||||
return &testLogger{
|
|
||||||
name: l.name + "." + name,
|
|
||||||
keyValues: l.keyValues,
|
|
||||||
writer: l.writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *testLogger) WithValues(kvs ...interface{}) logr.Logger {
|
|
||||||
newMap := make(map[string]interface{}, len(l.keyValues)+len(kvs)/2)
|
|
||||||
for k, v := range l.keyValues {
|
|
||||||
newMap[k] = v
|
|
||||||
}
|
|
||||||
for i := 0; i < len(kvs); i += 2 {
|
|
||||||
newMap[kvs[i].(string)] = kvs[i+1]
|
|
||||||
}
|
|
||||||
return &testLogger{
|
|
||||||
name: l.name,
|
|
||||||
keyValues: newMap,
|
|
||||||
writer: l.writer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,8 +46,6 @@ type HorizontalRunnerAutoscalerReconciler struct {
|
|||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Recorder record.EventRecorder
|
Recorder record.EventRecorder
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
|
|
||||||
CacheDuration time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerdeployments,verbs=get;list;watch;update;patch
|
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerdeployments,verbs=get;list;watch;update;patch
|
||||||
@@ -81,23 +79,13 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var replicas *int
|
replicas, err := r.computeReplicas(rd, hra)
|
||||||
|
if err != nil {
|
||||||
|
r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
|
||||||
|
|
||||||
replicasFromCache := r.getDesiredReplicasFromCache(hra)
|
log.Error(err, "Could not compute replicas")
|
||||||
|
|
||||||
if replicasFromCache != nil {
|
return ctrl.Result{}, err
|
||||||
replicas = replicasFromCache
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
replicas, err = r.computeReplicas(rd, hra)
|
|
||||||
if err != nil {
|
|
||||||
r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
|
|
||||||
|
|
||||||
log.Error(err, "Could not compute replicas")
|
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultReplicas = 1
|
const defaultReplicas = 1
|
||||||
@@ -105,18 +93,6 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||||||
currentDesiredReplicas := getIntOrDefault(rd.Spec.Replicas, defaultReplicas)
|
currentDesiredReplicas := getIntOrDefault(rd.Spec.Replicas, defaultReplicas)
|
||||||
newDesiredReplicas := getIntOrDefault(replicas, defaultReplicas)
|
newDesiredReplicas := getIntOrDefault(replicas, defaultReplicas)
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for _, reservation := range hra.Spec.CapacityReservations {
|
|
||||||
if reservation.ExpirationTime.Time.After(now) {
|
|
||||||
newDesiredReplicas += reservation.Replicas
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hra.Spec.MaxReplicas != nil && *hra.Spec.MaxReplicas < newDesiredReplicas {
|
|
||||||
newDesiredReplicas = *hra.Spec.MaxReplicas
|
|
||||||
}
|
|
||||||
|
|
||||||
// Please add more conditions that we can in-place update the newest runnerreplicaset without disruption
|
// Please add more conditions that we can in-place update the newest runnerreplicaset without disruption
|
||||||
if currentDesiredReplicas != newDesiredReplicas {
|
if currentDesiredReplicas != newDesiredReplicas {
|
||||||
copy := rd.DeepCopy()
|
copy := rd.DeepCopy()
|
||||||
@@ -127,12 +103,12 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||||||
|
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var updated *v1alpha1.HorizontalRunnerAutoscaler
|
|
||||||
|
|
||||||
if hra.Status.DesiredReplicas == nil || *hra.Status.DesiredReplicas != *replicas {
|
if hra.Status.DesiredReplicas == nil || *hra.Status.DesiredReplicas != *replicas {
|
||||||
updated = hra.DeepCopy()
|
updated := hra.DeepCopy()
|
||||||
|
|
||||||
if (hra.Status.DesiredReplicas == nil && *replicas > 1) ||
|
if (hra.Status.DesiredReplicas == nil && *replicas > 1) ||
|
||||||
(hra.Status.DesiredReplicas != nil && *replicas > *hra.Status.DesiredReplicas) {
|
(hra.Status.DesiredReplicas != nil && *replicas > *hra.Status.DesiredReplicas) {
|
||||||
@@ -141,37 +117,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated.Status.DesiredReplicas = replicas
|
updated.Status.DesiredReplicas = replicas
|
||||||
}
|
|
||||||
|
|
||||||
if replicasFromCache == nil {
|
|
||||||
if updated == nil {
|
|
||||||
updated = hra.DeepCopy()
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheEntries []v1alpha1.CacheEntry
|
|
||||||
|
|
||||||
for _, ent := range updated.Status.CacheEntries {
|
|
||||||
if ent.ExpirationTime.Before(&metav1.Time{Time: now}) {
|
|
||||||
cacheEntries = append(cacheEntries, ent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheDuration time.Duration
|
|
||||||
|
|
||||||
if r.CacheDuration > 0 {
|
|
||||||
cacheDuration = r.CacheDuration
|
|
||||||
} else {
|
|
||||||
cacheDuration = 10 * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
updated.Status.CacheEntries = append(updated.Status.CacheEntries, v1alpha1.CacheEntry{
|
|
||||||
Key: v1alpha1.CacheEntryKeyDesiredReplicas,
|
|
||||||
Value: *replicas,
|
|
||||||
ExpirationTime: metav1.Time{Time: time.Now().Add(cacheDuration)},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if updated != nil {
|
|
||||||
if err := r.Status().Update(ctx, updated); err != nil {
|
if err := r.Status().Update(ctx, updated); err != nil {
|
||||||
log.Error(err, "Failed to update horizontalrunnerautoscaler status")
|
log.Error(err, "Failed to update horizontalrunnerautoscaler status")
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/google/go-github/v33/github"
|
|
||||||
github3 "github.com/google/go-github/v33/github"
|
|
||||||
github2 "github.com/summerwind/actions-runner-controller/github"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/summerwind/actions-runner-controller/github/fake"
|
"github.com/summerwind/actions-runner-controller/github/fake"
|
||||||
@@ -35,12 +30,6 @@ var (
|
|||||||
workflowRunsFor1Replicas = `{"total_count": 6, "workflow_runs":[{"status":"queued"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}]}"`
|
workflowRunsFor1Replicas = `{"total_count": 6, "workflow_runs":[{"status":"queued"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}, {"status":"completed"}]}"`
|
||||||
)
|
)
|
||||||
|
|
||||||
var webhookServer *httptest.Server
|
|
||||||
|
|
||||||
var ghClient *github2.Client
|
|
||||||
|
|
||||||
var fakeRunnerList *fake.RunnersList
|
|
||||||
|
|
||||||
// SetupIntegrationTest will set up a testing environment.
|
// SetupIntegrationTest will set up a testing environment.
|
||||||
// This includes:
|
// This includes:
|
||||||
// * creating a Namespace to be used during the test
|
// * creating a Namespace to be used during the test
|
||||||
@@ -52,13 +41,10 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
|||||||
ns := &corev1.Namespace{}
|
ns := &corev1.Namespace{}
|
||||||
|
|
||||||
responses := &fake.FixedResponses{}
|
responses := &fake.FixedResponses{}
|
||||||
responses.ListRunners = fake.DefaultListRunnersHandler()
|
|
||||||
responses.ListRepositoryWorkflowRuns = &fake.Handler{
|
responses.ListRepositoryWorkflowRuns = &fake.Handler{
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Body: workflowRunsFor3Replicas,
|
Body: workflowRunsFor3Replicas,
|
||||||
}
|
}
|
||||||
fakeRunnerList = fake.NewRunnersList()
|
|
||||||
responses.ListRunners = fakeRunnerList.HandleList()
|
|
||||||
fakeGithubServer := fake.NewServer(fake.WithFixedResponses(responses))
|
fakeGithubServer := fake.NewServer(fake.WithFixedResponses(responses))
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@@ -73,7 +59,9 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
|||||||
mgr, err := ctrl.NewManager(cfg, ctrl.Options{})
|
mgr, err := ctrl.NewManager(cfg, ctrl.Options{})
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to create manager")
|
Expect(err).NotTo(HaveOccurred(), "failed to create manager")
|
||||||
|
|
||||||
ghClient = newGithubClient(fakeGithubServer)
|
runnersList = fake.NewRunnersList()
|
||||||
|
server = runnersList.GetServer()
|
||||||
|
ghClient := newGithubClient(server)
|
||||||
|
|
||||||
replicasetController := &RunnerReplicaSetReconciler{
|
replicasetController := &RunnerReplicaSetReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
@@ -97,30 +85,15 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
|||||||
client := newGithubClient(fakeGithubServer)
|
client := newGithubClient(fakeGithubServer)
|
||||||
|
|
||||||
autoscalerController := &HorizontalRunnerAutoscalerReconciler{
|
autoscalerController := &HorizontalRunnerAutoscalerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: scheme.Scheme,
|
Scheme: scheme.Scheme,
|
||||||
Log: logf.Log,
|
Log: logf.Log,
|
||||||
GitHubClient: client,
|
GitHubClient: client,
|
||||||
Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"),
|
Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"),
|
||||||
CacheDuration: 1 * time.Second,
|
|
||||||
}
|
}
|
||||||
err = autoscalerController.SetupWithManager(mgr)
|
err = autoscalerController.SetupWithManager(mgr)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
||||||
autoscalerWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{
|
|
||||||
Client: mgr.GetClient(),
|
|
||||||
Scheme: scheme.Scheme,
|
|
||||||
Log: logf.Log,
|
|
||||||
Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"),
|
|
||||||
}
|
|
||||||
err = autoscalerWebhook.SetupWithManager(mgr)
|
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to setup autoscaler webhook")
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("/", autoscalerWebhook.Handle)
|
|
||||||
|
|
||||||
webhookServer = httptest.NewServer(mux)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer GinkgoRecover()
|
defer GinkgoRecover()
|
||||||
|
|
||||||
@@ -133,7 +106,6 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
|||||||
close(stopCh)
|
close(stopCh)
|
||||||
|
|
||||||
fakeGithubServer.Close()
|
fakeGithubServer.Close()
|
||||||
webhookServer.Close()
|
|
||||||
|
|
||||||
err := k8sClient.Delete(ctx, ns)
|
err := k8sClient.Delete(ctx, ns)
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace")
|
Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace")
|
||||||
@@ -142,7 +114,7 @@ func SetupIntegrationTest(ctx context.Context) *testEnvironment {
|
|||||||
return &testEnvironment{Namespace: ns, Responses: responses}
|
return &testEnvironment{Namespace: ns, Responses: responses}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Context("INTEGRATION: Inside of a new namespace", func() {
|
var _ = Context("Inside of a new namespace", func() {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
env := SetupIntegrationTest(ctx)
|
env := SetupIntegrationTest(ctx)
|
||||||
ns := env.Namespace
|
ns := env.Namespace
|
||||||
@@ -263,20 +235,8 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() {
|
|||||||
},
|
},
|
||||||
MinReplicas: intPtr(1),
|
MinReplicas: intPtr(1),
|
||||||
MaxReplicas: intPtr(3),
|
MaxReplicas: intPtr(3),
|
||||||
ScaleDownDelaySecondsAfterScaleUp: intPtr(1),
|
ScaleDownDelaySecondsAfterScaleUp: nil,
|
||||||
Metrics: nil,
|
Metrics: nil,
|
||||||
ScaleUpTriggers: []actionsv1alpha1.ScaleUpTrigger{
|
|
||||||
{
|
|
||||||
GitHubEvent: &actionsv1alpha1.GitHubEventScaleUpTriggerSpec{
|
|
||||||
PullRequest: &actionsv1alpha1.PullRequestSpec{
|
|
||||||
Types: []string{"created"},
|
|
||||||
Branches: []string{"main"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Amount: 1,
|
|
||||||
Duration: metav1.Duration{Duration: time.Minute},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,33 +274,8 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() {
|
|||||||
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(3))
|
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
var runnerList actionsv1alpha1.RunnerList
|
|
||||||
|
|
||||||
err := k8sClient.List(ctx, &runnerList, client.InNamespace(ns.Name))
|
|
||||||
if err != nil {
|
|
||||||
logf.Log.Error(err, "list runners")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, r := range runnerList.Items {
|
|
||||||
fakeRunnerList.Add(&github3.Runner{
|
|
||||||
ID: github.Int64(int64(i)),
|
|
||||||
Name: github.String(r.Name),
|
|
||||||
OS: github.String("linux"),
|
|
||||||
Status: github.String("online"),
|
|
||||||
Busy: github.Bool(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
rs, err := ghClient.ListRunners(context.Background(), "", "", "test/valid")
|
|
||||||
Expect(err).NotTo(HaveOccurred(), "verifying list fake runners response")
|
|
||||||
Expect(len(rs)).To(Equal(3), "count of fake list runners")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale-down to 1 replica
|
// Scale-down to 1 replica
|
||||||
{
|
{
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas
|
responses.ListRepositoryWorkflowRuns.Body = workflowRunsFor1Replicas
|
||||||
|
|
||||||
var hra actionsv1alpha1.HorizontalRunnerAutoscaler
|
var hra actionsv1alpha1.HorizontalRunnerAutoscaler
|
||||||
@@ -373,60 +308,7 @@ var _ = Context("INTEGRATION: Inside of a new namespace", func() {
|
|||||||
|
|
||||||
return *runnerSets.Items[0].Spec.Replicas
|
return *runnerSets.Items[0].Spec.Replicas
|
||||||
},
|
},
|
||||||
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(1), "runners after HRA force update for scale-down")
|
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(1))
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
resp, err := sendWebhook(webhookServer, "pull_request", &github.PullRequestEvent{
|
|
||||||
PullRequest: &github.PullRequest{
|
|
||||||
Base: &github.PullRequestBranch{
|
|
||||||
Ref: github.String("main"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Repo: &github.Repository{
|
|
||||||
Name: github.String("test/valid"),
|
|
||||||
Organization: &github.Organization{
|
|
||||||
Name: github.String("test"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: github.String("created"),
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to send pull_request event")
|
|
||||||
|
|
||||||
Expect(resp.StatusCode).To(Equal(200))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale-up to 2 replicas
|
|
||||||
{
|
|
||||||
runnerSets := actionsv1alpha1.RunnerReplicaSetList{Items: []actionsv1alpha1.RunnerReplicaSet{}}
|
|
||||||
|
|
||||||
Eventually(
|
|
||||||
func() int {
|
|
||||||
err := k8sClient.List(ctx, &runnerSets, client.InNamespace(ns.Name))
|
|
||||||
if err != nil {
|
|
||||||
logf.Log.Error(err, "list runner sets")
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(runnerSets.Items)
|
|
||||||
},
|
|
||||||
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(1), "runner sets after webhook")
|
|
||||||
|
|
||||||
Eventually(
|
|
||||||
func() int {
|
|
||||||
err := k8sClient.List(ctx, &runnerSets, client.InNamespace(ns.Name))
|
|
||||||
if err != nil {
|
|
||||||
logf.Log.Error(err, "list runner sets")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(runnerSets.Items) == 0 {
|
|
||||||
logf.Log.Info("No runnerreplicasets exist yet")
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return *runnerSets.Items[0].Spec.Replicas
|
|
||||||
},
|
|
||||||
time.Second*5, time.Millisecond*500).Should(BeEquivalentTo(2), "runners after webhook")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,15 +18,12 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
gogithub "github.com/google/go-github/v33/github"
|
|
||||||
"github.com/summerwind/actions-runner-controller/hash"
|
"github.com/summerwind/actions-runner-controller/hash"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
@@ -44,8 +41,6 @@ const (
|
|||||||
finalizerName = "runner.actions.summerwind.dev"
|
finalizerName = "runner.actions.summerwind.dev"
|
||||||
|
|
||||||
LabelKeyPodTemplateHash = "pod-template-hash"
|
LabelKeyPodTemplateHash = "pod-template-hash"
|
||||||
|
|
||||||
retryDelayOnGitHubAPIRateLimitError = 30 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunnerReconciler reconciles a Runner object
|
// RunnerReconciler reconciles a Runner object
|
||||||
@@ -100,22 +95,9 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
|
|
||||||
if removed {
|
if removed {
|
||||||
if len(runner.Status.Registration.Token) > 0 {
|
if len(runner.Status.Registration.Token) > 0 {
|
||||||
ok, err := r.unregisterRunner(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
ok, err := r.unregisterRunner(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, &gogithub.RateLimitError{}) {
|
log.Error(err, "Failed to unregister runner")
|
||||||
// We log the underlying error when we failed calling GitHub API to list or unregisters,
|
|
||||||
// or the runner is still busy.
|
|
||||||
log.Error(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Failed to unregister runner due to GitHub API rate limits. Delaying retry for %s to avoid excessive GitHub API calls",
|
|
||||||
retryDelayOnGitHubAPIRateLimitError,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return ctrl.Result{RequeueAfter: retryDelayOnGitHubAPIRateLimitError}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +124,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
|
|
||||||
var pod corev1.Pod
|
var pod corev1.Pod
|
||||||
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
|
||||||
if !kerrors.IsNotFound(err) {
|
if !errors.IsNotFound(err) {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,39 +167,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !pod.ObjectMeta.DeletionTimestamp.IsZero() {
|
if !pod.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
deletionTimeout := 1 * time.Minute
|
return ctrl.Result{}, err
|
||||||
currentTime := time.Now()
|
|
||||||
deletionDidTimeout := currentTime.Sub(pod.DeletionTimestamp.Add(deletionTimeout)) > 0
|
|
||||||
|
|
||||||
if deletionDidTimeout {
|
|
||||||
log.Info(
|
|
||||||
"Pod failed to delete itself in a timely manner. "+
|
|
||||||
"This is typically the case when a Kubernetes node became unreachable "+
|
|
||||||
"and the kube controller started evicting nodes. Forcefully deleting the pod to not get stuck.",
|
|
||||||
"podDeletionTimestamp", pod.DeletionTimestamp,
|
|
||||||
"currentTime", currentTime,
|
|
||||||
"configuredDeletionTimeout", deletionTimeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
var force int64 = 0
|
|
||||||
// forcefully delete runner as we would otherwise get stuck if the node stays unreachable
|
|
||||||
if err := r.Delete(ctx, &pod, &client.DeleteOptions{GracePeriodSeconds: &force}); err != nil {
|
|
||||||
// probably
|
|
||||||
if !kerrors.IsNotFound(err) {
|
|
||||||
log.Error(err, "Failed to forcefully delete pod resource ...")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
// forceful deletion finally succeeded
|
|
||||||
return ctrl.Result{Requeue: true}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Recorder.Event(&runner, corev1.EventTypeNormal, "PodDeleted", fmt.Sprintf("Forcefully deleted pod '%s'", pod.Name))
|
|
||||||
log.Info("Forcefully deleted runner pod", "repository", runner.Spec.Repository)
|
|
||||||
// give kube manager a little time to forcefully delete the stuck pod
|
|
||||||
return ctrl.Result{RequeueAfter: 3 * time.Second}, err
|
|
||||||
} else {
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pod.Status.Phase == corev1.PodRunning {
|
if pod.Status.Phase == corev1.PodRunning {
|
||||||
@@ -244,33 +194,10 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
notRegistered := false
|
runnerBusy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||||
|
|
||||||
runnerBusy, err := r.GitHubClient.IsRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var e *github.RunnerNotFound
|
log.Error(err, "Failed to check if runner is busy")
|
||||||
if errors.As(err, &e) {
|
return ctrl.Result{}, nil
|
||||||
log.Error(err, "Failed to check if runner is busy. Probably this runner has never been successfully registered to GitHub.")
|
|
||||||
|
|
||||||
notRegistered = true
|
|
||||||
} else {
|
|
||||||
var e *gogithub.RateLimitError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
// We log the underlying error when we failed calling GitHub API to list or unregisters,
|
|
||||||
// or the runner is still busy.
|
|
||||||
log.Error(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Failed to check if runner is busy due to Github API rate limit. Retrying in %s to avoid excessive GitHub API calls",
|
|
||||||
retryDelayOnGitHubAPIRateLimitError,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return ctrl.Result{RequeueAfter: retryDelayOnGitHubAPIRateLimitError}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See the `newPod` function called above for more information
|
// See the `newPod` function called above for more information
|
||||||
@@ -282,27 +209,9 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
restart = true
|
restart = true
|
||||||
}
|
}
|
||||||
|
|
||||||
registrationTimeout := 10 * time.Minute
|
|
||||||
currentTime := time.Now()
|
|
||||||
registrationDidTimeout := currentTime.Sub(pod.CreationTimestamp.Add(registrationTimeout)) > 0
|
|
||||||
|
|
||||||
if notRegistered && registrationDidTimeout {
|
|
||||||
log.Info(
|
|
||||||
"Runner failed to register itself to GitHub in timely manner. "+
|
|
||||||
"Recreating the pod to see if it resolves the issue. "+
|
|
||||||
"CAUTION: If you see this a lot, you should investigate the root cause. "+
|
|
||||||
"See https://github.com/summerwind/actions-runner-controller/issues/288",
|
|
||||||
"podCreationTimestamp", pod.CreationTimestamp,
|
|
||||||
"currentTime", currentTime,
|
|
||||||
"configuredRegistrationTimeout", registrationTimeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
restart = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't do anything if there's no need to restart the runner
|
// Don't do anything if there's no need to restart the runner
|
||||||
if !restart {
|
if !restart {
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete current pod if recreation is needed
|
// Delete current pod if recreation is needed
|
||||||
@@ -318,8 +227,23 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunnerReconciler) unregisterRunner(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
|
func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) {
|
||||||
runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
|
runners, err := r.GitHubClient.ListRunners(ctx, org, repo)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, runner := range runners {
|
||||||
|
if runner.GetName() == name {
|
||||||
|
return runner.GetBusy(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("runner not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name string) (bool, error) {
|
||||||
|
runners, err := r.GitHubClient.ListRunners(ctx, org, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -339,7 +263,7 @@ func (r *RunnerReconciler) unregisterRunner(ctx context.Context, enterprise, org
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.GitHubClient.RemoveRunner(ctx, enterprise, org, repo, id); err != nil {
|
if err := r.GitHubClient.RemoveRunner(ctx, org, repo, id); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +277,7 @@ func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v
|
|||||||
|
|
||||||
log := r.Log.WithValues("runner", runner.Name)
|
log := r.Log.WithValues("runner", runner.Name)
|
||||||
|
|
||||||
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
|
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
|
||||||
log.Error(err, "Failed to get new registration token")
|
log.Error(err, "Failed to get new registration token")
|
||||||
@@ -415,10 +339,6 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
|||||||
Name: "RUNNER_REPO",
|
Name: "RUNNER_REPO",
|
||||||
Value: runner.Spec.Repository,
|
Value: runner.Spec.Repository,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "RUNNER_ENTERPRISE",
|
|
||||||
Value: runner.Spec.Enterprise,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "RUNNER_LABELS",
|
Name: "RUNNER_LABELS",
|
||||||
Value: strings.Join(runner.Spec.Labels, ","),
|
Value: strings.Join(runner.Spec.Labels, ","),
|
||||||
@@ -584,7 +504,6 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
|||||||
SecurityContext: &corev1.SecurityContext{
|
SecurityContext: &corev1.SecurityContext{
|
||||||
Privileged: &privileged,
|
Privileged: &privileged,
|
||||||
},
|
},
|
||||||
Resources: runner.Spec.DockerdContainerResources,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (r *RunnerDeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
|||||||
rs := oldSets[i]
|
rs := oldSets[i]
|
||||||
|
|
||||||
if err := r.Client.Delete(ctx, &rs); err != nil {
|
if err := r.Client.Delete(ctx, &rs); err != nil {
|
||||||
log.Error(err, "Failed to delete runnerreplicaset resource")
|
log.Error(err, "Failed to delete runner resource")
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,10 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
gogithub "github.com/google/go-github/v33/github"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
@@ -68,7 +65,7 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
|||||||
|
|
||||||
var allRunners v1alpha1.RunnerList
|
var allRunners v1alpha1.RunnerList
|
||||||
if err := r.List(ctx, &allRunners, client.InNamespace(req.Namespace)); err != nil {
|
if err := r.List(ctx, &allRunners, client.InNamespace(req.Namespace)); err != nil {
|
||||||
if !kerrors.IsNotFound(err) {
|
if !errors.IsNotFound(err) {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,51 +102,12 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
|||||||
// get runners that are currently not busy
|
// get runners that are currently not busy
|
||||||
var notBusy []v1alpha1.Runner
|
var notBusy []v1alpha1.Runner
|
||||||
for _, runner := range myRunners {
|
for _, runner := range myRunners {
|
||||||
busy, err := r.GitHubClient.IsRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
busy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notRegistered := false
|
log.Error(err, "Failed to check if runner is busy")
|
||||||
|
return ctrl.Result{}, err
|
||||||
var e *github.RunnerNotFound
|
}
|
||||||
if errors.As(err, &e) {
|
if !busy {
|
||||||
log.Error(err, "Failed to check if runner is busy. Probably this runner has never been successfully registered to GitHub, and therefore we prioritize it for deletion", "runnerName", runner.Name)
|
|
||||||
notRegistered = true
|
|
||||||
} else {
|
|
||||||
var e *gogithub.RateLimitError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
// We log the underlying error when we failed calling GitHub API to list or unregisters,
|
|
||||||
// or the runner is still busy.
|
|
||||||
log.Error(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf(
|
|
||||||
"Failed to check if runner is busy due to GitHub API rate limit. Retrying in %s to avoid excessive GitHub API calls",
|
|
||||||
retryDelayOnGitHubAPIRateLimitError,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return ctrl.Result{RequeueAfter: retryDelayOnGitHubAPIRateLimitError}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
registrationTimeout := 15 * time.Minute
|
|
||||||
currentTime := time.Now()
|
|
||||||
registrationDidTimeout := currentTime.Sub(runner.CreationTimestamp.Add(registrationTimeout)) > 0
|
|
||||||
|
|
||||||
if notRegistered && registrationDidTimeout {
|
|
||||||
log.Info(
|
|
||||||
"Runner failed to register itself to GitHub in timely manner. "+
|
|
||||||
"Recreating the pod to see if it resolves the issue. "+
|
|
||||||
"CAUTION: If you see this a lot, you should investigate the root cause. "+
|
|
||||||
"See https://github.com/summerwind/actions-runner-controller/issues/288",
|
|
||||||
"runnerCreationTimestamp", runner.CreationTimestamp,
|
|
||||||
"currentTime", currentTime,
|
|
||||||
"configuredRegistrationTimeout", registrationTimeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
notBusy = append(notBusy, runner)
|
|
||||||
}
|
|
||||||
} else if !busy {
|
|
||||||
notBusy = append(notBusy, runner)
|
notBusy = append(notBusy, runner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +117,7 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
if err := r.Client.Delete(ctx, ¬Busy[i]); client.IgnoreNotFound(err) != nil {
|
if err := r.Client.Delete(ctx, ¬Busy[i]); err != nil {
|
||||||
log.Error(err, "Failed to delete runner resource")
|
log.Error(err, "Failed to delete runner resource")
|
||||||
|
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
@@ -228,3 +186,19 @@ func (r *RunnerReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
|||||||
Owns(&v1alpha1.Runner{}).
|
Owns(&v1alpha1.Runner{}).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RunnerReplicaSetReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) {
|
||||||
|
runners, err := r.GitHubClient.ListRunners(ctx, org, repo)
|
||||||
|
r.Log.Info("runners", "github", runners)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, runner := range runners {
|
||||||
|
if runner.GetName() == name {
|
||||||
|
return runner.GetBusy(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("runner not found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ limitations under the License.
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/onsi/ginkgo/config"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -45,8 +43,6 @@ var testEnv *envtest.Environment
|
|||||||
func TestAPIs(t *testing.T) {
|
func TestAPIs(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
|
|
||||||
config.GinkgoConfig.FocusString = os.Getenv("GINKGO_FOCUS")
|
|
||||||
|
|
||||||
RunSpecsWithDefaultAndCustomReporters(t,
|
RunSpecsWithDefaultAndCustomReporters(t,
|
||||||
"Controller Suite",
|
"Controller Suite",
|
||||||
[]Reporter{envtest.NewlineReporter{}})
|
[]Reporter{envtest.NewlineReporter{}})
|
||||||
|
|||||||
@@ -24,16 +24,6 @@ const (
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListRunnersHandler struct {
|
|
||||||
Status int
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ListRunnersHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.WriteHeader(h.Status)
|
|
||||||
fmt.Fprintf(w, h.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
Status int
|
Status int
|
||||||
Body string
|
Body string
|
||||||
@@ -104,7 +94,10 @@ func NewServer(opts ...Option) *httptest.Server {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// For ListRunners
|
// For ListRunners
|
||||||
"/repos/test/valid/actions/runners": config.FixedResponses.ListRunners,
|
"/repos/test/valid/actions/runners": &Handler{
|
||||||
|
Status: http.StatusOK,
|
||||||
|
Body: RunnersListBody,
|
||||||
|
},
|
||||||
"/repos/test/invalid/actions/runners": &Handler{
|
"/repos/test/invalid/actions/runners": &Handler{
|
||||||
Status: http.StatusNoContent,
|
Status: http.StatusNoContent,
|
||||||
Body: "",
|
Body: "",
|
||||||
@@ -166,10 +159,3 @@ func NewServer(opts ...Option) *httptest.Server {
|
|||||||
|
|
||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultListRunnersHandler() *ListRunnersHandler {
|
|
||||||
return &ListRunnersHandler{
|
|
||||||
Status: http.StatusOK,
|
|
||||||
Body: RunnersListBody,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
package fake
|
package fake
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type FixedResponses struct {
|
type FixedResponses struct {
|
||||||
ListRepositoryWorkflowRuns *Handler
|
ListRepositoryWorkflowRuns *Handler
|
||||||
ListWorkflowJobs *MapHandler
|
ListWorkflowJobs *MapHandler
|
||||||
ListRunners http.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(*ServerConfig)
|
type Option func(*ServerConfig)
|
||||||
@@ -28,15 +25,6 @@ func WithListWorkflowJobsResponse(status int, bodies map[int]string) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithListRunnersResponse(status int, body string) Option {
|
|
||||||
return func(c *ServerConfig) {
|
|
||||||
c.FixedResponses.ListRunners = &ListRunnersHandler{
|
|
||||||
Status: status,
|
|
||||||
Body: body,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithFixedResponses(responses *FixedResponses) Option {
|
func WithFixedResponses(responses *FixedResponses) Option {
|
||||||
return func(c *ServerConfig) {
|
return func(c *ServerConfig) {
|
||||||
c.FixedResponses = responses
|
c.FixedResponses = responses
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ func (r *RunnersList) Add(runner *github.Runner) {
|
|||||||
func (r *RunnersList) GetServer() *httptest.Server {
|
func (r *RunnersList) GetServer() *httptest.Server {
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
|
|
||||||
router.Handle("/repos/{owner}/{repo}/actions/runners", r.HandleList())
|
router.Handle("/repos/{owner}/{repo}/actions/runners", r.handleList())
|
||||||
router.Handle("/repos/{owner}/{repo}/actions/runners/{id}", r.handleRemove())
|
router.Handle("/repos/{owner}/{repo}/actions/runners/{id}", r.handleRemove())
|
||||||
router.Handle("/orgs/{org}/actions/runners", r.HandleList())
|
router.Handle("/orgs/{org}/actions/runners", r.handleList())
|
||||||
router.Handle("/orgs/{org}/actions/runners/{id}", r.handleRemove())
|
router.Handle("/orgs/{org}/actions/runners/{id}", r.handleRemove())
|
||||||
|
|
||||||
return httptest.NewServer(router)
|
return httptest.NewServer(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunnersList) HandleList() http.HandlerFunc {
|
func (r *RunnersList) handleList() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, res *http.Request) {
|
return func(w http.ResponseWriter, res *http.Request) {
|
||||||
j, err := json.Marshal(github.Runners{
|
j, err := json.Marshal(github.Runners{
|
||||||
TotalCount: len(r.runners),
|
TotalCount: len(r.runners),
|
||||||
|
|||||||
119
github/github.go
119
github/github.go
@@ -78,7 +78,7 @@ func (c *Config) NewClient() (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRegistrationToken returns a registration token tied with the name of repository and runner.
|
// GetRegistrationToken returns a registration token tied with the name of repository and runner.
|
||||||
func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo, name string) (*github.RegistrationToken, error) {
|
func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name string) (*github.RegistrationToken, error) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
@@ -89,13 +89,13 @@ func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo
|
|||||||
return rt, nil
|
return rt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
|
owner, repo, err := getOwnerAndRepo(org, repo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rt, err
|
return rt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rt, res, err := c.createRegistrationToken(ctx, enterprise, owner, repo)
|
rt, res, err := c.createRegistrationToken(ctx, owner, repo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create registration token: %v", err)
|
return nil, fmt.Errorf("failed to create registration token: %v", err)
|
||||||
@@ -114,17 +114,17 @@ func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRunner removes a runner with specified runner ID from repository.
|
// RemoveRunner removes a runner with specified runner ID from repository.
|
||||||
func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) error {
|
func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID int64) error {
|
||||||
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
|
owner, repo, err := getOwnerAndRepo(org, repo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.removeRunner(ctx, enterprise, owner, repo, runnerID)
|
res, err := c.removeRunner(ctx, owner, repo, runnerID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to remove runner: %w", err)
|
return fmt.Errorf("failed to remove runner: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != 204 {
|
if res.StatusCode != 204 {
|
||||||
@@ -135,8 +135,8 @@ func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListRunners returns a list of runners of specified owner/repository name.
|
// ListRunners returns a list of runners of specified owner/repository name.
|
||||||
func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string) ([]*github.Runner, error) {
|
func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.Runner, error) {
|
||||||
enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
|
owner, repo, err := getOwnerAndRepo(org, repo)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -146,10 +146,10 @@ func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string)
|
|||||||
|
|
||||||
opts := github.ListOptions{PerPage: 10}
|
opts := github.ListOptions{PerPage: 10}
|
||||||
for {
|
for {
|
||||||
list, res, err := c.listRunners(ctx, enterprise, owner, repo, &opts)
|
list, res, err := c.listRunners(ctx, owner, repo, &opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return runners, fmt.Errorf("failed to list runners: %w", err)
|
return runners, fmt.Errorf("failed to list runners: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runners = append(runners, list.Runners...)
|
runners = append(runners, list.Runners...)
|
||||||
@@ -174,80 +174,42 @@ func (c *Client) cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrappers for github functions (switch between enterprise/organization/repository mode)
|
// wrappers for github functions (switch between organization/repository mode)
|
||||||
// so the calling functions don't need to switch and their code is a bit cleaner
|
// so the calling functions don't need to switch and their code is a bit cleaner
|
||||||
|
|
||||||
func (c *Client) createRegistrationToken(ctx context.Context, enterprise, org, repo string) (*github.RegistrationToken, *github.Response, error) {
|
func (c *Client) createRegistrationToken(ctx context.Context, owner, 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.Client.Actions.CreateRegistrationToken(ctx, owner, repo)
|
||||||
}
|
}
|
||||||
if len(org) > 0 {
|
|
||||||
return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, org)
|
return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, owner)
|
||||||
}
|
|
||||||
return c.Client.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, owner, 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.Client.Actions.RemoveRunner(ctx, owner, repo, runnerID)
|
||||||
}
|
}
|
||||||
if len(org) > 0 {
|
|
||||||
return c.Client.Actions.RemoveOrganizationRunner(ctx, org, runnerID)
|
return c.Client.Actions.RemoveOrganizationRunner(ctx, owner, runnerID)
|
||||||
}
|
|
||||||
return c.Client.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, owner, 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.Client.Actions.ListRunners(ctx, owner, repo, opts)
|
||||||
}
|
}
|
||||||
if len(org) > 0 {
|
|
||||||
return c.Client.Actions.ListOrganizationRunners(ctx, org, opts)
|
return c.Client.Actions.ListOrganizationRunners(ctx, owner, opts)
|
||||||
}
|
|
||||||
return c.Client.Enterprise.ListRunners(ctx, enterprise, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListRepositoryWorkflowRuns(ctx context.Context, user string, repoName string) ([]*github.WorkflowRun, error) {
|
// Validates owner and repo arguments. Both are optional, but at least one should be specified
|
||||||
c.Client.Actions.ListRepositoryWorkflowRuns(ctx, user, repoName, nil)
|
func getOwnerAndRepo(org, repo string) (string, string, error) {
|
||||||
|
|
||||||
var workflowRuns []*github.WorkflowRun
|
|
||||||
|
|
||||||
opts := github.ListWorkflowRunsOptions{
|
|
||||||
ListOptions: github.ListOptions{
|
|
||||||
PerPage: 100,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
list, res, err := c.Client.Actions.ListRepositoryWorkflowRuns(ctx, user, repoName, &opts)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return workflowRuns, fmt.Errorf("failed to list workflow runs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowRuns = append(workflowRuns, list.WorkflowRuns...)
|
|
||||||
if res.NextPage == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
opts.Page = res.NextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
return workflowRuns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates enterprise, organisation and repo arguments. Both are optional, but at least one should be specified
|
|
||||||
func getEnterpriseOrganisationAndRepo(enterprise, org, repo string) (string, string, string, error) {
|
|
||||||
if len(repo) > 0 {
|
if len(repo) > 0 {
|
||||||
owner, repository, err := splitOwnerAndRepo(repo)
|
return splitOwnerAndRepo(repo)
|
||||||
return "", owner, repository, err
|
|
||||||
}
|
}
|
||||||
if len(org) > 0 {
|
if len(org) > 0 {
|
||||||
return "", org, "", nil
|
return org, "", nil
|
||||||
}
|
}
|
||||||
if len(enterprise) > 0 {
|
return "", "", fmt.Errorf("organization and repository are both empty")
|
||||||
return enterprise, "", "", nil
|
|
||||||
}
|
|
||||||
return "", "", "", fmt.Errorf("enterprise, organization and repository are all empty")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRegistrationKey(org, repo string) string {
|
func getRegistrationKey(org, repo string) string {
|
||||||
@@ -282,26 +244,3 @@ func getEnterpriseApiUrl(baseURL string) (string, error) {
|
|||||||
// Trim trailing slash, otherwise there's double slash added to token endpoint
|
// Trim trailing slash, otherwise there's double slash added to token endpoint
|
||||||
return fmt.Sprintf("%s://%s%s", baseEndpoint.Scheme, baseEndpoint.Host, strings.TrimSuffix(baseEndpoint.Path, "/")), nil
|
return fmt.Sprintf("%s://%s%s", baseEndpoint.Scheme, baseEndpoint.Host, strings.TrimSuffix(baseEndpoint.Path, "/")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunnerNotFound struct {
|
|
||||||
runnerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RunnerNotFound) Error() string {
|
|
||||||
return fmt.Sprintf("runner %q not found", e.runnerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Client) IsRunnerBusy(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
|
|
||||||
runners, err := r.ListRunners(ctx, enterprise, org, repo)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, runner := range runners {
|
|
||||||
if runner.GetName() == name {
|
|
||||||
return runner.GetBusy(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, &RunnerNotFound{runnerName: name}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,36 +32,29 @@ func newTestClient() *Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
res := &fake.FixedResponses{
|
server = fake.NewServer()
|
||||||
ListRunners: fake.DefaultListRunnersHandler(),
|
|
||||||
}
|
|
||||||
server = fake.NewServer(fake.WithFixedResponses(res))
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRegistrationToken(t *testing.T) {
|
func TestGetRegistrationToken(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
enterprise string
|
org string
|
||||||
org string
|
repo string
|
||||||
repo string
|
token string
|
||||||
token string
|
err bool
|
||||||
err bool
|
|
||||||
}{
|
}{
|
||||||
{enterprise: "", org: "", repo: "test/valid", token: fake.RegistrationToken, err: false},
|
{org: "", repo: "test/valid", token: fake.RegistrationToken, err: false},
|
||||||
{enterprise: "", org: "", repo: "test/invalid", token: "", err: true},
|
{org: "", repo: "test/invalid", token: "", err: true},
|
||||||
{enterprise: "", org: "", repo: "test/error", token: "", err: true},
|
{org: "", repo: "test/error", token: "", err: true},
|
||||||
{enterprise: "", org: "test", repo: "", token: fake.RegistrationToken, err: false},
|
{org: "test", repo: "", token: fake.RegistrationToken, err: false},
|
||||||
{enterprise: "", org: "invalid", repo: "", token: "", err: true},
|
{org: "invalid", repo: "", token: "", err: true},
|
||||||
{enterprise: "", org: "error", repo: "", token: "", err: true},
|
{org: "error", repo: "", token: "", err: true},
|
||||||
{enterprise: "test", org: "", repo: "", token: fake.RegistrationToken, err: false},
|
|
||||||
{enterprise: "invalid", org: "", repo: "", token: "", err: true},
|
|
||||||
{enterprise: "error", org: "", repo: "", token: "", err: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newTestClient()
|
client := newTestClient()
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
rt, err := client.GetRegistrationToken(context.Background(), tt.enterprise, tt.org, tt.repo, "test")
|
rt, err := client.GetRegistrationToken(context.Background(), tt.org, tt.repo, "test")
|
||||||
if !tt.err && err != nil {
|
if !tt.err && err != nil {
|
||||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||||
}
|
}
|
||||||
@@ -73,26 +66,22 @@ func TestGetRegistrationToken(t *testing.T) {
|
|||||||
|
|
||||||
func TestListRunners(t *testing.T) {
|
func TestListRunners(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
enterprise string
|
org string
|
||||||
org string
|
repo string
|
||||||
repo string
|
length int
|
||||||
length int
|
err bool
|
||||||
err bool
|
|
||||||
}{
|
}{
|
||||||
{enterprise: "", org: "", repo: "test/valid", length: 2, err: false},
|
{org: "", repo: "test/valid", length: 2, err: false},
|
||||||
{enterprise: "", org: "", repo: "test/invalid", length: 0, err: true},
|
{org: "", repo: "test/invalid", length: 0, err: true},
|
||||||
{enterprise: "", org: "", repo: "test/error", length: 0, err: true},
|
{org: "", repo: "test/error", length: 0, err: true},
|
||||||
{enterprise: "", org: "test", repo: "", length: 2, err: false},
|
{org: "test", repo: "", length: 2, err: false},
|
||||||
{enterprise: "", org: "invalid", repo: "", length: 0, err: true},
|
{org: "invalid", repo: "", length: 0, err: true},
|
||||||
{enterprise: "", org: "error", repo: "", length: 0, err: true},
|
{org: "error", repo: "", length: 0, err: true},
|
||||||
{enterprise: "test", org: "", repo: "", length: 2, err: false},
|
|
||||||
{enterprise: "invalid", org: "", repo: "", length: 0, err: true},
|
|
||||||
{enterprise: "error", org: "", repo: "", length: 0, err: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newTestClient()
|
client := newTestClient()
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
runners, err := client.ListRunners(context.Background(), tt.enterprise, tt.org, tt.repo)
|
runners, err := client.ListRunners(context.Background(), tt.org, tt.repo)
|
||||||
if !tt.err && err != nil {
|
if !tt.err && err != nil {
|
||||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||||
}
|
}
|
||||||
@@ -104,25 +93,21 @@ func TestListRunners(t *testing.T) {
|
|||||||
|
|
||||||
func TestRemoveRunner(t *testing.T) {
|
func TestRemoveRunner(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
enterprise string
|
org string
|
||||||
org string
|
repo string
|
||||||
repo string
|
err bool
|
||||||
err bool
|
|
||||||
}{
|
}{
|
||||||
{enterprise: "", org: "", repo: "test/valid", err: false},
|
{org: "", repo: "test/valid", err: false},
|
||||||
{enterprise: "", org: "", repo: "test/invalid", err: true},
|
{org: "", repo: "test/invalid", err: true},
|
||||||
{enterprise: "", org: "", repo: "test/error", err: true},
|
{org: "", repo: "test/error", err: true},
|
||||||
{enterprise: "", org: "test", repo: "", err: false},
|
{org: "test", repo: "", err: false},
|
||||||
{enterprise: "", org: "invalid", repo: "", err: true},
|
{org: "invalid", repo: "", err: true},
|
||||||
{enterprise: "", org: "error", repo: "", err: true},
|
{org: "error", repo: "", err: true},
|
||||||
{enterprise: "test", org: "", repo: "", err: false},
|
|
||||||
{enterprise: "invalid", org: "", repo: "", err: true},
|
|
||||||
{enterprise: "error", org: "", repo: "", err: true},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newTestClient()
|
client := newTestClient()
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
err := client.RemoveRunner(context.Background(), tt.enterprise, tt.org, tt.repo, int64(1))
|
err := client.RemoveRunner(context.Background(), tt.org, tt.repo, int64(1))
|
||||||
if !tt.err && err != nil {
|
if !tt.err && err != nil {
|
||||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -6,7 +6,10 @@ require (
|
|||||||
github.com/bradleyfalzon/ghinstallation v1.1.1
|
github.com/bradleyfalzon/ghinstallation v1.1.1
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-logr/logr v0.1.0
|
github.com/go-logr/logr v0.1.0
|
||||||
github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a
|
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||||
|
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
|
||||||
|
github.com/google/go-github/v33 v33.0.0
|
||||||
|
github.com/google/go-querystring v1.0.0
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/onsi/ginkgo v1.8.0
|
github.com/onsi/ginkgo v1.8.0
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -116,10 +116,14 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
|||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
|
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
|
||||||
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
|
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
|
||||||
github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a h1:Z9Nzq8ntvvXCLnFGOkzzcD8HDOzOo+obuwE5oK85vNQ=
|
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04 h1:wEYk2h/GwOhImcVjiTIceP88WxVbXw2F+ARYUQMEsfg=
|
||||||
github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||||
|
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
|
||||||
|
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
|
|||||||
1089
index.yaml
Normal file
1089
index.yaml
Normal file
File diff suppressed because it is too large
Load Diff
9
main.go
9
main.go
@@ -144,11 +144,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
horizontalRunnerAutoscaler := &controllers.HorizontalRunnerAutoscalerReconciler{
|
horizontalRunnerAutoscaler := &controllers.HorizontalRunnerAutoscalerReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Log: ctrl.Log.WithName("controllers").WithName("HorizontalRunnerAutoscaler"),
|
Log: ctrl.Log.WithName("controllers").WithName("HorizontalRunnerAutoscaler"),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
GitHubClient: ghClient,
|
GitHubClient: ghClient,
|
||||||
CacheDuration: syncPeriod - 10*time.Second,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = horizontalRunnerAutoscaler.SetupWithManager(mgr); err != nil {
|
if err = horizontalRunnerAutoscaler.SetupWithManager(mgr); err != nil {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > .env \
|
|||||||
&& chmod g+rwx /opt/hostedtoolcache
|
&& chmod g+rwx /opt/hostedtoolcache
|
||||||
|
|
||||||
COPY entrypoint.sh /
|
COPY entrypoint.sh /
|
||||||
COPY --chown=runner:docker patched $RUNNER_ASSETS_DIR/patched
|
COPY patched $RUNNER_ASSETS_DIR/patched
|
||||||
|
|
||||||
USER runner
|
USER runner
|
||||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
|
|||||||
|
|
||||||
VOLUME /var/lib/docker
|
VOLUME /var/lib/docker
|
||||||
|
|
||||||
COPY --chown=runner:docker patched $RUNNER_ASSETS_DIR/patched
|
COPY patched $RUNNER_ASSETS_DIR/patched
|
||||||
|
|
||||||
# No group definition, as that makes it harder to run docker.
|
# No group definition, as that makes it harder to run docker.
|
||||||
USER runner
|
USER runner
|
||||||
|
|||||||
@@ -16,16 +16,14 @@ if [ -z "${RUNNER_NAME}" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ] && [ -n "${RUNNER_ENTERPRISE}" ]; then
|
if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ]; then
|
||||||
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
|
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
|
||||||
elif [ -n "${RUNNER_ORG}" ]; then
|
elif [ -n "${RUNNER_ORG}" ]; then
|
||||||
ATTACH="${RUNNER_ORG}"
|
ATTACH="${RUNNER_ORG}"
|
||||||
elif [ -n "${RUNNER_REPO}" ]; then
|
elif [ -n "${RUNNER_REPO}" ]; then
|
||||||
ATTACH="${RUNNER_REPO}"
|
ATTACH="${RUNNER_REPO}"
|
||||||
elif [ -n "${RUNNER_ENTERPRISE}" ]; then
|
|
||||||
ATTACH="enterprises/${RUNNER_ENTERPRISE}"
|
|
||||||
else
|
else
|
||||||
echo "At least one of RUNNER_ORG or RUNNER_REPO or RUNNER_ENTERPRISE must be set" 1>&2
|
echo "At least one of RUNNER_ORG or RUNNER_REPO must be set" 1>&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user