mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 19:50:30 +00:00
Compare commits
231 Commits
v0.24.0
...
actions-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332548093a | ||
|
|
b4e143dadc | ||
|
|
93c4dd856e | ||
|
|
93aea48c38 | ||
|
|
14b17cca73 | ||
|
|
5298c6ea29 | ||
|
|
8fa08d59b1 | ||
|
|
003c552c34 | ||
|
|
83370d7f95 | ||
|
|
7da9d0ae19 | ||
|
|
b56fa6a748 | ||
|
|
a22ee8a5f1 | ||
|
|
e1762ba746 | ||
|
|
710e2fbc3a | ||
|
|
433552770e | ||
|
|
ba2a32eef6 | ||
|
|
de0d7ad78c | ||
|
|
0382f3bbd5 | ||
|
|
b6640a033c | ||
|
|
998c028d90 | ||
|
|
f8dffab19d | ||
|
|
7ff5b7da8c | ||
|
|
6aaff4ecee | ||
|
|
437d0173b0 | ||
|
|
a389292478 | ||
|
|
6eadb03669 | ||
|
|
aa60021ab0 | ||
|
|
50e26bd2f6 | ||
|
|
2dd13b4a19 | ||
|
|
35af24cf03 | ||
|
|
ca96b66fbe | ||
|
|
4db5fbc7a1 | ||
|
|
add83bc7bc | ||
|
|
666bba784c | ||
|
|
0672ff0ff9 | ||
|
|
f2f22827a6 | ||
|
|
a3a801757d | ||
|
|
0c003f20d4 | ||
|
|
863760828a | ||
|
|
517fae4119 | ||
|
|
d9e1e64dc6 | ||
|
|
3c4ab2d479 | ||
|
|
3ca96557a6 | ||
|
|
5fd6ec4bc8 | ||
|
|
6863bdb208 | ||
|
|
f3fcb428ae | ||
|
|
41bae32a9f | ||
|
|
e4879e7ae4 | ||
|
|
e5bb130fda | ||
|
|
e7a21cfc53 | ||
|
|
8f54644b08 | ||
|
|
c56d6a6c85 | ||
|
|
a96c3e1102 | ||
|
|
d29de8d454 | ||
|
|
12c4d96250 | ||
|
|
46a13c0626 | ||
|
|
e32a8054d0 | ||
|
|
0deb6809b9 | ||
|
|
21af1ec19d | ||
|
|
dfadb86d66 | ||
|
|
c91e76f169 | ||
|
|
718232f8f4 | ||
|
|
c7f5f7d161 | ||
|
|
33bb6902bc | ||
|
|
aeb0601147 | ||
|
|
991c0b3211 | ||
|
|
71da6d5271 | ||
|
|
e4fd4bc99c | ||
|
|
d9a8dc7e84 | ||
|
|
795cf8b1de | ||
|
|
0615c2adb1 | ||
|
|
a918e56ece | ||
|
|
546b5251ed | ||
|
|
74dda4ea1b | ||
|
|
b29816290a | ||
|
|
921daff61b | ||
|
|
e233f7ad6a | ||
|
|
623c84fa52 | ||
|
|
d4fb6204cb | ||
|
|
f8e07c7fe4 | ||
|
|
f73713859c | ||
|
|
e0a7be253e | ||
|
|
915739b972 | ||
|
|
4925880e5e | ||
|
|
c143fd50b5 | ||
|
|
dbd668ae2d | ||
|
|
5c1be3265b | ||
|
|
ebcd838501 | ||
|
|
6ef276b239 | ||
|
|
f70f325f48 | ||
|
|
f7c336f9dd | ||
|
|
ae380f5987 | ||
|
|
4bf1c12a98 | ||
|
|
cb561d8db4 | ||
|
|
eaf6d2f2e2 | ||
|
|
5ae7ce16e0 | ||
|
|
bdcde44642 | ||
|
|
5116e3800e | ||
|
|
4e107a4e50 | ||
|
|
93238697d9 | ||
|
|
48f62b4c89 | ||
|
|
ea94b3cc5b | ||
|
|
0cac005ab2 | ||
|
|
55ca7bfdf5 | ||
|
|
ca97f39fcb | ||
|
|
f0c8c07428 | ||
|
|
e54edea918 | ||
|
|
e58f82bfce | ||
|
|
244e0dd987 | ||
|
|
02009cef17 | ||
|
|
2b5af62184 | ||
|
|
ec58ad19e0 | ||
|
|
cc9fe33ef5 | ||
|
|
4a5a85fd61 | ||
|
|
56b26fd751 | ||
|
|
36e95dad47 | ||
|
|
3724b46033 | ||
|
|
538e2783d7 | ||
|
|
72ca998266 | ||
|
|
d439ed5c81 | ||
|
|
58c2bdf2bb | ||
|
|
fe9164b025 | ||
|
|
06141b39b4 | ||
|
|
ac4c3fd365 | ||
|
|
dc29e31bcc | ||
|
|
784019f3d7 | ||
|
|
fc55477c1c | ||
|
|
3f78f71137 | ||
|
|
e511401e51 | ||
|
|
37aa1a0b8c | ||
|
|
bea0775bec | ||
|
|
79a494b2aa | ||
|
|
97404144eb | ||
|
|
b77489d098 | ||
|
|
4152afbd30 | ||
|
|
29f621e1c8 | ||
|
|
5651ba6ead | ||
|
|
759cc4b47f | ||
|
|
4ede0c18d0 | ||
|
|
9091d9b756 | ||
|
|
a09c2564d9 | ||
|
|
a555c90fd5 | ||
|
|
38644cf4e8 | ||
|
|
23f357db10 | ||
|
|
584745b67d | ||
|
|
df9592dc99 | ||
|
|
8071ac7066 | ||
|
|
3c33eca501 | ||
|
|
aa827474b2 | ||
|
|
c75c9f9226 | ||
|
|
c09a04ec01 | ||
|
|
618276e3d3 | ||
|
|
18dd89c884 | ||
|
|
98b17dc0a5 | ||
|
|
c658dcfa6d | ||
|
|
c4996d4bbd | ||
|
|
7a3fa4f362 | ||
|
|
1bfd743e69 | ||
|
|
734f3bd63a | ||
|
|
409dc4c114 | ||
|
|
4b9a6c6700 | ||
|
|
86e1a4a8f3 | ||
|
|
544d620bc3 | ||
|
|
1cfe1974c4 | ||
|
|
7e4b6ebd6d | ||
|
|
11cb9b7882 | ||
|
|
10b88bf070 | ||
|
|
8b619e7c6f | ||
|
|
fea1457f12 | ||
|
|
473295e3fc | ||
|
|
9f6f962fc7 | ||
|
|
2a475f25c7 | ||
|
|
dd9f25ea78 | ||
|
|
b8e4eee904 | ||
|
|
edbdef8d20 | ||
|
|
a190fa97bb | ||
|
|
bfc5ea4727 | ||
|
|
5a9e8545aa | ||
|
|
4446ba57e1 | ||
|
|
d62c8a4697 | ||
|
|
946d5b1fa7 | ||
|
|
da6b07660e | ||
|
|
e3deb0d752 | ||
|
|
82641e5036 | ||
|
|
2fe6adf5b7 | ||
|
|
736126b793 | ||
|
|
6abf5bbac8 | ||
|
|
dc4f116bda | ||
|
|
cda10fd243 | ||
|
|
b5d1a63bdf | ||
|
|
6f3e23973d | ||
|
|
a517c1ff66 | ||
|
|
9b28e633c1 | ||
|
|
8161136cbd | ||
|
|
a9ac5a1cbf | ||
|
|
d4f35cff4f | ||
|
|
f661249f07 | ||
|
|
73e430ce54 | ||
|
|
858ef8979d | ||
|
|
1ce0a183a6 | ||
|
|
63935d2053 | ||
|
|
fc63d6d26e | ||
|
|
5ea08411e6 | ||
|
|
067ed2e5ec | ||
|
|
d86bd2bcd7 | ||
|
|
ddd417f756 | ||
|
|
0386c0734c | ||
|
|
af96de6184 | ||
|
|
abb8615796 | ||
|
|
bc7a3cab1b | ||
|
|
e2c8163b8c | ||
|
|
84d16c1c12 | ||
|
|
071898c96b | ||
|
|
f24e2fa44e | ||
|
|
3c7d3d6b57 | ||
|
|
23f091d7fa | ||
|
|
667764e027 | ||
|
|
de693c4191 | ||
|
|
510fc9c834 | ||
|
|
7fd5e24961 | ||
|
|
9974b1a2b7 | ||
|
|
bd91b73fd9 | ||
|
|
a7ae910ee4 | ||
|
|
2733c36d0e | ||
|
|
0ef9a22cd4 | ||
|
|
933b0c7888 | ||
|
|
1b7ec33135 | ||
|
|
a62882d243 | ||
|
|
0cd13fe51d | ||
|
|
01c8dc237e | ||
|
|
7c4db63718 |
53
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
53
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,8 +1,18 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "Bug"
|
||||
title: "<Please write what didn't work for you here>"
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: read-troubleshooting-guide
|
||||
attributes:
|
||||
label: Checks
|
||||
description: Please check all the boxes below before submitting
|
||||
options:
|
||||
- label: I've already read https://github.com/actions-runner-controller/actions-runner-controller/blob/master/TROUBLESHOOTING.md and I'm sure my issue is not covered in the troubleshooting guide.
|
||||
required: true
|
||||
- label: I'm not using a custom entrypoint in my runner image
|
||||
required: true
|
||||
- type: input
|
||||
id: controller-version
|
||||
attributes:
|
||||
@@ -17,6 +27,12 @@ body:
|
||||
label: Helm Chart Version
|
||||
description: Run `helm list` and see what's shown under CHART VERSION. Any release tags prefixed with `actions-runner-controller-` are for chart releases
|
||||
placeholder: ex. 0.11.0
|
||||
- type: input
|
||||
id: cert-manager-version
|
||||
attributes:
|
||||
label: CertManager Version
|
||||
description: Run `kubectl get po -o yaml $CERT_MANAGER_POD` and see the image tag, or run `helm list` and see what's shown under APP VERSION for your cert-manager Helm release.
|
||||
placeholder: ex. 1.8
|
||||
- type: dropdown
|
||||
id: deployment-method
|
||||
attributes:
|
||||
@@ -29,11 +45,22 @@ body:
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: cert-manager
|
||||
attributes:
|
||||
label: cert-manager installation
|
||||
description: Confirm that you've installed cert-manager correctly by answering a few questions
|
||||
placeholder: |
|
||||
- Did you follow https://github.com/actions-runner-controller/actions-runner-controller#installation? If not, describe the installation process so that we can reproduce your environment.
|
||||
- Are you sure you've installed cert-manager from an official source?
|
||||
(Note that we won't provide user support for cert-manager itself. Make sure cert-manager is fully working before testing ARC or reporting a bug
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Checks
|
||||
description: Please check the boxes below before submitting
|
||||
description: Please check all the boxes below before submitting
|
||||
options:
|
||||
- label: This isn't a question or user support case (For Q&A and community support, go to [Discussions](https://github.com/actions-runner-controller/actions-runner-controller/discussions). It might also be a good idea to contract with any of contributors and maintainers if your business is so critical and therefore you need priority support
|
||||
required: true
|
||||
@@ -41,7 +68,9 @@ body:
|
||||
required: true
|
||||
- label: My actions-runner-controller version (v0.x.y) does support the feature
|
||||
required: true
|
||||
- label: I've already upgraded ARC to the latest and it didn't fix the issue
|
||||
- label: I've already upgraded ARC (including the CRDs, see charts/actions-runner-controller/docs/UPGRADING.md for details) to the latest and it didn't fix the issue
|
||||
required: true
|
||||
- label: I've migrated to the workflow job webhook event (if you using webhook driven scaling)
|
||||
required: true
|
||||
- type: textarea
|
||||
id: resource-definitions
|
||||
@@ -112,10 +141,12 @@ body:
|
||||
- type: textarea
|
||||
id: controller-logs
|
||||
attributes:
|
||||
label: Controller Logs
|
||||
description: "Include logs from `actions-runner-controller`'s controller-manager pod"
|
||||
label: Whole Controller Logs
|
||||
description: "NEVER EVER OMIT THIS! Include logs from `actions-runner-controller`'s controller-manager pod. Don't omit the parts you think irrelevant!"
|
||||
render: shell
|
||||
placeholder: |
|
||||
PROVIDE THE LOGS VIA A GIST LINK (https://gist.github.com/), NOT DIRECTLY IN THIS TEXT AREA
|
||||
|
||||
To grab controller logs:
|
||||
|
||||
# Set NS according to your setup
|
||||
@@ -125,17 +156,17 @@ body:
|
||||
kubectl -n $NS get po
|
||||
|
||||
kubectl -n $NS logs $POD_NAME > arc.log
|
||||
|
||||
Upload it to e.g. https://gist.github.com/ and paste the link to it here.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: runner-pod-logs
|
||||
attributes:
|
||||
label: Runner Pod Logs
|
||||
description: "Include logs from runner pod(s)"
|
||||
label: Whole Runner Pod Logs
|
||||
description: "Include logs from runner pod(s). Please don't omit the parts you think irrelevant!"
|
||||
render: shell
|
||||
placeholder: |
|
||||
PROVIDE THE WHOLE LOGS VIA A GIST LINK (https://gist.github.com/), NOT DIRECTLY IN THIS TEXT AREA
|
||||
|
||||
To grab the runner pod logs:
|
||||
|
||||
# Set NS according to your setup. It should match your RunnerDeployment's metadata.namespace.
|
||||
@@ -146,8 +177,8 @@ body:
|
||||
|
||||
kubectl -n $NS logs $POD_NAME -c runner > runnerpod_runner.log
|
||||
kubectl -n $NS logs $POD_NAME -c docker > runnerpod_docker.log
|
||||
|
||||
Upload it to e.g. https://gist.github.com/ and paste the link to it here.
|
||||
|
||||
If any of the containers are getting terminated immediately, try adding `--previous` to the kubectl-logs command to obtain logs emitted before the termination.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,4 @@
|
||||
# Blank issues are mainly for maintainers who are known to write complete issue descriptions without need to following a form
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Sponsor ARC Maintainers
|
||||
about: If your business relies on the continued maintainance of actions-runner-controller, please consider sponsoring the project and the maintainers.
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,19 +1,21 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
labels: enhancement
|
||||
title: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
### What would you like added?
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
*A clear and concise description of what you want to happen.*
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
Note: Feature requests to integrate vendor specific cloud tools (e.g. `awscli`, `gcloud-sdk`, `azure-cli`) will likely be rejected as the Runner image aims to be vendor agnostic.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
### Why is this needed?
|
||||
|
||||
*A clear and concise description of any alternative solutions or features you've considered.*
|
||||
|
||||
### Additional context
|
||||
|
||||
*Add any other context or screenshots about the feature request here.*
|
||||
|
||||
@@ -37,15 +37,15 @@ runs:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.ref == 'master' && github.event.pull_request.merged == true }}
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ inputs.username }}
|
||||
password: ${{ inputs.password }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ github.event_name == 'release' || github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: docker/login-action@v2
|
||||
if: ${{ github.ref == 'master' && github.event.pull_request.merged == true }}
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ inputs.ghcr_username }}
|
||||
|
||||
5
.github/renovate.json5
vendored
5
.github/renovate.json5
vendored
@@ -13,7 +13,7 @@
|
||||
{
|
||||
// use https://github.com/actions/runner/releases
|
||||
"fileMatch": [
|
||||
".github/workflows/runners.yml"
|
||||
".github/workflows/runners.yaml"
|
||||
],
|
||||
"matchStrings": ["RUNNER_VERSION: +(?<currentValue>.*?)\\n"],
|
||||
"depNameTemplate": "actions/runner",
|
||||
@@ -31,7 +31,8 @@
|
||||
{
|
||||
"fileMatch": [
|
||||
"runner/actions-runner.dockerfile",
|
||||
"runner/actions-runner-dind.dockerfile"
|
||||
"runner/actions-runner-dind.dockerfile",
|
||||
"runner/actions-runner-dind-rootless.dockerfile"
|
||||
],
|
||||
"matchStrings": ["RUNNER_VERSION=+(?<currentValue>.*?)\\n"],
|
||||
"depNameTemplate": "actions/runner",
|
||||
|
||||
23
.github/workflows/golangci-lint.yaml
vendored
Normal file
23
.github/workflows/golangci-lint.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
only-new-issues: true
|
||||
version: v1.49.0
|
||||
@@ -1,24 +1,26 @@
|
||||
name: Publish Controller Image
|
||||
name: Publish ARC
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
types:
|
||||
- published
|
||||
|
||||
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
release-controller:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USER }}
|
||||
steps:
|
||||
- name: Set outputs
|
||||
id: vars
|
||||
run: echo ::set-output name=sha_short::${GITHUB_SHA::7}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@193b404f8a1d1dccaf6ed9bf03cdb68d2d02020f
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.2'
|
||||
|
||||
@@ -39,31 +41,31 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: make github-release
|
||||
run: |
|
||||
make github-release
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@0522dcd2bf084920c411162fde334a308be75015
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@91cb32d715c128e5f0ede915cd7e196ab7799b83
|
||||
- name: Setup Docker Environment
|
||||
id: vars
|
||||
uses: ./.github/actions/setup-docker-environment
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@d398f07826957cd0a18ea1b059cf1207835e60bc
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
ghcr_username: ${{ github.actor }}
|
||||
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@c5e6528d5ddefc82f682165021e05edf58044bce
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: VERSION=${{ env.VERSION }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:latest
|
||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}
|
||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}-${{ steps.vars.outputs.sha_short }}
|
||||
|
||||
ghcr.io/actions-runner-controller/actions-runner-controller:latest
|
||||
ghcr.io/actions-runner-controller/actions-runner-controller:${{ env.VERSION }}
|
||||
ghcr.io/actions-runner-controller/actions-runner-controller:${{ env.VERSION }}-${{ steps.vars.outputs.sha_short }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
59
.github/workflows/publish-canary.yaml
vendored
Normal file
59
.github/workflows/publish-canary.yaml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Publish Canary Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/workflows/validate-chart.yaml'
|
||||
- '.github/workflows/publish-chart.yaml'
|
||||
- '.github/workflows/publish-arc.yaml'
|
||||
- '.github/workflows/runners.yaml'
|
||||
- '.github/workflows/validate-entrypoint.yaml'
|
||||
- '.github/renovate.*'
|
||||
- 'runner/**'
|
||||
- '.gitignore'
|
||||
- 'PROJECT'
|
||||
- 'LICENSE'
|
||||
- 'Makefile'
|
||||
|
||||
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
canary-build:
|
||||
name: Build and Publish Canary Image
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USER }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Environment
|
||||
id: vars
|
||||
uses: ./.github/actions/setup-docker-environment
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
ghcr_username: ${{ github.actor }}
|
||||
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Considered unstable builds
|
||||
# See Issue #285, PR #286, and PR #323 for more information
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: VERSION=canary-${{ github.sha }}
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:canary
|
||||
ghcr.io/${{ github.repository }}:canary
|
||||
cache-from: type=gha,scope=arc-canary
|
||||
cache-to: type=gha,mode=max,scope=arc-canary
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish helm chart
|
||||
name: Publish Helm Chart
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
- master
|
||||
paths:
|
||||
- 'charts/**'
|
||||
- '.github/workflows/on-push-master-publish-chart.yml'
|
||||
- '.github/workflows/publish-chart.yaml'
|
||||
- '!charts/actions-runner-controller/docs/**'
|
||||
- '!**.md'
|
||||
workflow_dispatch:
|
||||
@@ -20,18 +20,18 @@ permissions:
|
||||
|
||||
jobs:
|
||||
lint-chart:
|
||||
runs-on: ubuntu-latest
|
||||
name: Lint Chart
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
publish-chart: ${{ steps.publish-chart-step.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@217bf70cbd2e930ba2e81ba7e1de2f7faecc42ba
|
||||
uses: azure/setup-helm@v3.3
|
||||
with:
|
||||
version: ${{ env.HELM_VERSION }}
|
||||
|
||||
@@ -52,12 +52,12 @@ jobs:
|
||||
--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@fff15a21cc8b16191cb1249f621fa3a55b9005b8
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@62a185010be4cb08459f7acb19f37927235d5cf3
|
||||
uses: helm/chart-testing-action@v2.3.1
|
||||
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
@@ -68,22 +68,23 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config charts/.ci/ct-config.yaml
|
||||
run: |
|
||||
ct lint --config charts/.ci/ct-config.yaml
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@94729529f85113b88f4f819c17ce61382e6d8478
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
uses: helm/kind-action@v1.4.0
|
||||
|
||||
# We need cert-manager already installed in the cluster because we assume the CRDs exist
|
||||
- name: Install cert-manager
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
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'
|
||||
run: ct install --config charts/.ci/ct-config.yaml
|
||||
|
||||
# WARNING: This relies on the latest release being inat the top of the JSON from GitHub and a clean chart.yaml
|
||||
- name: Check if Chart Publish is Needed
|
||||
@@ -100,16 +101,17 @@ jobs:
|
||||
fi
|
||||
|
||||
publish-chart:
|
||||
permissions:
|
||||
contents: write # for helm/chart-releaser-action to push chart release and create a release
|
||||
if: needs.lint-chart.outputs.publish-chart == 'true'
|
||||
needs: lint-chart
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish Chart
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # for helm/chart-releaser-action to push chart release and create a release
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -119,7 +121,7 @@ jobs:
|
||||
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||
|
||||
- name: Run chart-releaser
|
||||
uses: helm/chart-releaser-action@a3454e46a6f5ac4811069a381e646961dda2e1bf
|
||||
uses: helm/chart-releaser-action@v1.4.1
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
name: "Code Scanning"
|
||||
name: Run CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '30 1 * * 0'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2.1.11
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: go
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2.1.11
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2.1.11
|
||||
uses: github/codeql-action/analyze@v2
|
||||
29
.github/workflows/run-first-interaction.yaml
vendored
Normal file
29
.github/workflows/run-first-interaction.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: first-interaction
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check_for_first_interaction:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/first-interaction@main
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: |
|
||||
Hello! Thank you for filing an issue.
|
||||
|
||||
The maintainers will triage your issue shortly.
|
||||
|
||||
In the meantime, please take a look at the [troubleshooting guide](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/TROUBLESHOOTING.md) for bug reports.
|
||||
|
||||
If this is a feature request, please review our [contribution guidelines](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/CONTRIBUTING.md).
|
||||
pr-message: |
|
||||
Hello! Thank you for your contribution.
|
||||
|
||||
Please review our [contribution guidelines](https://github.com/actions-runner-controller/actions-runner-controller/blob/master/CONTRIBUTING.md) to understand the project's testing and code conventions.
|
||||
@@ -1,7 +1,6 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
name: Run Stale Bot
|
||||
on:
|
||||
schedule:
|
||||
# 01:30 every day
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
permissions:
|
||||
@@ -9,12 +8,13 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write # for actions/stale to close stale issues
|
||||
pull-requests: write # for actions/stale to close stale PRs
|
||||
name: Run Stale
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write # for actions/stale to close stale issues
|
||||
pull-requests: write # for actions/stale to close stale PRs
|
||||
steps:
|
||||
- uses: actions/stale@65d24b70926a596b0f0098d7e1eb572175d73bc1
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
# turn off stale for both issues and PRs
|
||||
@@ -6,27 +6,37 @@ on:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- closed
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'runner/**'
|
||||
- '!runner/Makefile'
|
||||
- .github/workflows/runners.yml
|
||||
- '.github/workflows/runners.yaml'
|
||||
- '!**.md'
|
||||
# We must do a trigger on a push: instead of a types: closed so GitHub Secrets
|
||||
# are available to the workflow run
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- 'runner/**'
|
||||
- '!runner/Makefile'
|
||||
- '.github/workflows/runners.yaml'
|
||||
- '!**.md'
|
||||
|
||||
env:
|
||||
RUNNER_VERSION: 2.292.0
|
||||
RUNNER_VERSION: 2.298.2
|
||||
DOCKER_VERSION: 20.10.12
|
||||
RUNNER_CONTAINER_HOOKS_VERSION: 0.1.2
|
||||
DOCKERHUB_USERNAME: summerwind
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-runners:
|
||||
name: Build ${{ matrix.name }}-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
name: Build ${{ matrix.name }}-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -37,10 +47,13 @@ jobs:
|
||||
- name: actions-runner-dind
|
||||
os-name: ubuntu
|
||||
os-version: 20.04
|
||||
- name: actions-runner-dind-rootless
|
||||
os-name: ubuntu
|
||||
os-version: 20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Docker Environment
|
||||
id: vars
|
||||
@@ -52,15 +65,16 @@ jobs:
|
||||
ghcr_password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push Versioned Tags
|
||||
uses: docker/build-push-action@c5e6528d5ddefc82f682165021e05edf58044bce
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./runner
|
||||
file: ./runner/${{ matrix.name }}.dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.ref == 'master' && github.event.pull_request.merged == true }}
|
||||
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
build-args: |
|
||||
RUNNER_VERSION=${{ env.RUNNER_VERSION }}
|
||||
DOCKER_VERSION=${{ env.DOCKER_VERSION }}
|
||||
RUNNER_CONTAINER_HOOKS_VERSION=${{ env.RUNNER_CONTAINER_HOOKS_VERSION }}
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ matrix.os-name }}-${{ matrix.os-version }}-${{ steps.vars.outputs.sha_short }}
|
||||
@@ -68,5 +82,5 @@ jobs:
|
||||
ghcr.io/${{ github.repository }}/${{ matrix.name }}:latest
|
||||
ghcr.io/${{ github.repository }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ matrix.os-name }}-${{ matrix.os-version }}
|
||||
ghcr.io/${{ github.repository }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ matrix.os-name }}-${{ matrix.os-version }}-${{ steps.vars.outputs.sha_short }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
cache-from: type=gha,scope=build-${{ matrix.name }}
|
||||
cache-to: type=gha,mode=max,scope=build-${{ matrix.name }}
|
||||
24
.github/workflows/test-entrypoint.yaml
vendored
24
.github/workflows/test-entrypoint.yaml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Unit tests for entrypoint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
paths:
|
||||
- 'runner/**'
|
||||
- 'test/entrypoint/**'
|
||||
- '!**.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Test entrypoint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
- name: Run unit tests for entrypoint.sh
|
||||
run: |
|
||||
make acceptance/runner/entrypoint
|
||||
@@ -1,48 +1,59 @@
|
||||
name: CI
|
||||
name: Validate ARC
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- .github/workflows/runners.yml
|
||||
- .github/workflows/on-push-lint-charts.yml
|
||||
- .github/workflows/on-push-master-publish-chart.yml
|
||||
- .github/workflows/release.yml
|
||||
- .github/workflows/test-entrypoint.yml
|
||||
- .github/workflows/wip.yml
|
||||
- 'runner/**'
|
||||
- '**.md'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/workflows/publish-canary.yaml'
|
||||
- '.github/workflows/validate-chart.yaml'
|
||||
- '.github/workflows/publish-chart.yaml'
|
||||
- '.github/workflows/runners.yaml'
|
||||
- '.github/workflows/publish-arc.yaml'
|
||||
- '.github/workflows/validate-entrypoint.yaml'
|
||||
- '.github/renovate.*'
|
||||
- 'runner/**'
|
||||
- '.gitignore'
|
||||
- 'PROJECT'
|
||||
- 'LICENSE'
|
||||
- 'Makefile'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
test-controller:
|
||||
name: Test ARC
|
||||
runs-on: ubuntu-latest
|
||||
name: Test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
- uses: actions/setup-go@193b404f8a1d1dccaf6ed9bf03cdb68d2d02020f
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set-up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18.2'
|
||||
check-latest: false
|
||||
- run: go version
|
||||
- uses: actions/cache@95f200e41cfa87b8e07f30196c0df17a67e67786
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Install kubebuilder
|
||||
run: |
|
||||
curl -L -O https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.2/kubebuilder_2.3.2_linux_amd64.tar.gz
|
||||
tar zxvf kubebuilder_2.3.2_linux_amd64.tar.gz
|
||||
sudo mv kubebuilder_2.3.2_linux_amd64 /usr/local/kubebuilder
|
||||
|
||||
- name: Run tests
|
||||
run: make test
|
||||
run: |
|
||||
make test
|
||||
|
||||
- name: Verify manifests are up-to-date
|
||||
run: |
|
||||
make manifests
|
||||
@@ -1,10 +1,10 @@
|
||||
name: Lint and Test Charts
|
||||
name: Validate Helm Chart
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'charts/**'
|
||||
- '.github/workflows/on-push-lint-charts.yml'
|
||||
- '.github/workflows/validate-chart.yaml'
|
||||
- '!charts/actions-runner-controller/docs/**'
|
||||
- '!**.md'
|
||||
workflow_dispatch:
|
||||
@@ -16,17 +16,17 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-latest
|
||||
validate-chart:
|
||||
name: Lint Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@217bf70cbd2e930ba2e81ba7e1de2f7faecc42ba
|
||||
uses: azure/setup-helm@v3.3
|
||||
with:
|
||||
version: ${{ env.HELM_VERSION }}
|
||||
|
||||
@@ -47,12 +47,12 @@ jobs:
|
||||
--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@fff15a21cc8b16191cb1249f621fa3a55b9005b8
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@62a185010be4cb08459f7acb19f37927235d5cf3
|
||||
uses: helm/chart-testing-action@v2.3.1
|
||||
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
@@ -63,18 +63,20 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config charts/.ci/ct-config.yaml
|
||||
run: |
|
||||
ct lint --config charts/.ci/ct-config.yaml
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@94729529f85113b88f4f819c17ce61382e6d8478
|
||||
uses: helm/kind-action@v1.4.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
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
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
|
||||
run: |
|
||||
ct install --config charts/.ci/ct-config.yaml
|
||||
45
.github/workflows/validate-runners.yaml
vendored
Normal file
45
.github/workflows/validate-runners.yaml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Validate Runners
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
paths:
|
||||
- 'runner/**'
|
||||
- 'test/entrypoint/**'
|
||||
- '!**.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
name: runner / shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: shellcheck
|
||||
uses: reviewdog/action-shellcheck@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path: "./runner"
|
||||
pattern: |
|
||||
*.sh
|
||||
*.bash
|
||||
update-status
|
||||
# Make this consistent with `make shellsheck`
|
||||
shellcheck_flags: "--shell bash --source-path runner"
|
||||
exclude: "./.git/*"
|
||||
check_all_files_with_shebangs: "false"
|
||||
# Set this to "true" once we addressed all the shellcheck findings
|
||||
fail_on_error: "false"
|
||||
test-runner-entrypoint:
|
||||
name: Test entrypoint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
make acceptance/runner/entrypoint
|
||||
54
.github/workflows/wip.yml
vendored
54
.github/workflows/wip.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Publish Canary Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- .github/workflows/runners.yml
|
||||
- .github/workflows/on-push-lint-charts.yml
|
||||
- .github/workflows/on-push-master-publish-chart.yml
|
||||
- .github/workflows/release.yml
|
||||
- .github/workflows/test-entrypoint.yml
|
||||
- "runner/**"
|
||||
- "**.md"
|
||||
- ".gitignore"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build and Publish Canary Image
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USER }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@0522dcd2bf084920c411162fde334a308be75015
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@91cb32d715c128e5f0ede915cd7e196ab7799b83
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@d398f07826957cd0a18ea1b059cf1207835e60bc
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
# Considered unstable builds
|
||||
# See Issue #285, PR #286, and PR #323 for more information
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@c5e6528d5ddefc82f682165021e05edf58044bce
|
||||
with:
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:canary
|
||||
17
.golangci.yaml
Normal file
17
.golangci.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
run:
|
||||
timeout: 3m
|
||||
output:
|
||||
format: github-actions
|
||||
linters-settings:
|
||||
errcheck:
|
||||
exclude-functions:
|
||||
- (net/http.ResponseWriter).Write
|
||||
- (*net/http.Server).Shutdown
|
||||
- (*github.com/actions-runner-controller/actions-runner-controller/simulator.VisibleRunnerGroups).Add
|
||||
- (*github.com/actions-runner-controller/actions-runner-controller/testing.Kind).Stop
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: controllers/suite_test.go
|
||||
linters:
|
||||
- staticcheck
|
||||
text: "SA1019"
|
||||
212
CONTRIBUTING.md
212
CONTRIBUTING.md
@@ -1,85 +1,53 @@
|
||||
## Contributing
|
||||
# Contribution Guide
|
||||
|
||||
### Testing Controller Built from a Pull Request
|
||||
- [Contribution Guide](#contribution-guide)
|
||||
- [Welcome](#welcome)
|
||||
- [Before contributing code](#before-contributing-code)
|
||||
- [How to Contribute a Patch](#how-to-contribute-a-patch)
|
||||
- [Developing the Controller](#developing-the-controller)
|
||||
- [Developing the Runners](#developing-the-runners)
|
||||
- [Tests](#tests)
|
||||
- [Running Ginkgo Tests](#running-ginkgo-tests)
|
||||
- [Running End to End Tests](#running-end-to-end-tests)
|
||||
- [Rerunning a failed test](#rerunning-a-failed-test)
|
||||
- [Testing in a non-kind cluster](#testing-in-a-non-kind-cluster)
|
||||
- [Code conventions](#code-conventions)
|
||||
- [Opening the Pull Request](#opening-the-pull-request)
|
||||
- [Helm Version Changes](#helm-version-changes)
|
||||
- [Testing Controller Built from a Pull Request](#testing-controller-built-from-a-pull-request)
|
||||
|
||||
We always appreciate your help in testing open pull requests by deploying custom builds of actions-runner-controller onto your own environment, so that we are extra sure we didn't break anything.
|
||||
## Welcome
|
||||
|
||||
It is especially true when the pull request is about GitHub Enterprise, both GHEC and GHES, as [maintainers don't have GitHub Enterprise environments for testing](/README.md#github-enterprise-support).
|
||||
This document is the single source of truth for how to contribute to the code base.
|
||||
Feel free to browse the [open issues](https://github.com/actions-runner-controller/actions-runner-controller/issues) or file a new one, all feedback is welcome!
|
||||
By reading this guide, we hope to give you all of the information you need to be able to pick up issues, contribute new features, and get your work
|
||||
reviewed and merged.
|
||||
|
||||
The process would look like the below:
|
||||
## Before contributing code
|
||||
|
||||
- Clone this repository locally
|
||||
- Checkout the branch. If you use the `gh` command, run `gh pr checkout $PR_NUMBER`
|
||||
- Run `NAME=$DOCKER_USER/actions-runner-controller VERSION=canary make docker-build docker-push` for a custom container image build
|
||||
- Update your actions-runner-controller's controller-manager deployment to use the new image, `$DOCKER_USER/actions-runner-controller:canary`
|
||||
We welcome code patches, but to make sure things are well coordinated you should discuss any significant change before starting the work.
|
||||
The maintainers ask that you signal your intention to contribute to the project using the issue tracker.
|
||||
If there is an existing issue that you want to work on, please let us know so we can get it assigned to you.
|
||||
If you noticed a bug or want to add a new feature, there are issue templates you can fill out.
|
||||
|
||||
Please also note that you need to replace `$DOCKER_USER` with your own DockerHub account name.
|
||||
When filing a feature request, the maintainers will review the change and give you a decision on whether we are willing to accept the feature into the project.
|
||||
For significantly large and/or complex features, we may request that you write up an architectural decision record ([ADR](https://github.blog/2020-08-13-why-write-adrs/)) detailing the change.
|
||||
Please use the [template](/adrs/0000-TEMPLATE.md) as guidance.
|
||||
|
||||
### How to Contribute a Patch
|
||||
<!--
|
||||
TODO: Add a pre-requisite section describing what developers should
|
||||
install in order get started on ARC.
|
||||
-->
|
||||
|
||||
Depending on what you are patching depends on how you should go about it. Below are some guides on how to test patches locally as well as develop the controller and runners.
|
||||
## How to Contribute a Patch
|
||||
|
||||
When submitting a PR for a change please provide evidence that your change works as we still need to work on improving the CI of the project. Some resources are provided for helping achieve this, see this guide for details.
|
||||
Depending on what you are patching depends on how you should go about it.
|
||||
Below are some guides on how to test patches locally as well as develop the controller and runners.
|
||||
|
||||
#### Running an End to End Test
|
||||
When submitting a PR for a change please provide evidence that your change works as we still need to work on improving the CI of the project.
|
||||
Some resources are provided for helping achieve this, see this guide for details.
|
||||
|
||||
> **Notes for Ubuntu 20.04+ users**
|
||||
>
|
||||
> If you're using Ubuntu 20.04 or greater, you might have installed `docker` with `snap`.
|
||||
>
|
||||
> If you want to stick with `snap`-provided `docker`, do not forget to set `TMPDIR` to
|
||||
> somewhere under `$HOME`.
|
||||
> Otherwise `kind load docker-image` fail while running `docker save`.
|
||||
> See https://kind.sigs.k8s.io/docs/user/known-issues/#docker-installed-with-snap for more information.
|
||||
|
||||
To test your local changes against both PAT and App based authentication please run the `acceptance` make target with the authentication configuration details provided:
|
||||
|
||||
```shell
|
||||
# This sets `VERSION` envvar to some appropriate value
|
||||
. hack/make-env.sh
|
||||
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
APP_ID=*** \
|
||||
PRIVATE_KEY_FILE_PATH=path/to/pem/file \
|
||||
INSTALLATION_ID=*** \
|
||||
make acceptance
|
||||
```
|
||||
|
||||
**Rerunning a failed test**
|
||||
|
||||
When one of tests run by `make acceptance` failed, you'd probably like to rerun only the failed one.
|
||||
|
||||
It can be done by `make acceptance/run` and by setting the combination of `ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm|kubectl` and `ACCEPTANCE_TEST_SECRET_TYPE=token|app` values that failed (note, you just need to set the corresponding authentication configuration in this circumstance)
|
||||
|
||||
In the example below, we rerun the test for the combination `ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm ACCEPTANCE_TEST_SECRET_TYPE=token` only:
|
||||
|
||||
```shell
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm
|
||||
ACCEPTANCE_TEST_SECRET_TYPE=token \
|
||||
make acceptance/run
|
||||
```
|
||||
|
||||
**Testing in a non-kind cluster**
|
||||
|
||||
If you prefer to test in a non-kind cluster, you can instead run:
|
||||
|
||||
```shell
|
||||
KUBECONFIG=path/to/kubeconfig \
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
APP_ID=*** \
|
||||
PRIVATE_KEY_FILE_PATH=path/to/pem/file \
|
||||
INSTALLATION_ID=*** \
|
||||
ACCEPTANCE_TEST_SECRET_TYPE=token \
|
||||
make docker-build acceptance/setup \
|
||||
acceptance/deploy \
|
||||
acceptance/tests
|
||||
```
|
||||
|
||||
#### Developing the Controller
|
||||
### Developing the Controller
|
||||
|
||||
Rerunning the whole acceptance test suite from scratch on every little change to the controller, the runner, and the chart would be counter-productive.
|
||||
|
||||
@@ -119,13 +87,14 @@ NAME=$DOCKER_USER/actions-runner make \
|
||||
(kubectl get po -ojsonpath={.items[*].metadata.name} | xargs -n1 kubectl delete po)
|
||||
```
|
||||
|
||||
#### Developing the Runners
|
||||
### Developing the Runners
|
||||
|
||||
**Tests**
|
||||
#### Tests
|
||||
|
||||
A set of example pipelines (./acceptance/pipelines) are provided in this repository which you can use to validate your runners are working as expected. When raising a PR please run the relevant suites to prove your change hasn't broken anything.
|
||||
A set of example pipelines (./acceptance/pipelines) are provided in this repository which you can use to validate your runners are working as expected.
|
||||
When raising a PR please run the relevant suites to prove your change hasn't broken anything.
|
||||
|
||||
**Running Ginkgo Tests**
|
||||
#### Running Ginkgo Tests
|
||||
|
||||
You can run the integration test suite that is written in Ginkgo with:
|
||||
|
||||
@@ -135,7 +104,8 @@ make test-with-deps
|
||||
|
||||
This will firstly install a few binaries required to setup the integration test environment and then runs `go test` to start the Ginkgo test.
|
||||
|
||||
If you don't want to use `make`, like when you're running tests from your IDE, install required binaries to `/usr/local/kubebuilder/bin`. That's the directory in which controller-runtime's `envtest` framework locates the binaries.
|
||||
If you don't want to use `make`, like when you're running tests from your IDE, install required binaries to `/usr/local/kubebuilder/bin`.
|
||||
That's the directory in which controller-runtime's `envtest` framework locates the binaries.
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /usr/local/kubebuilder/bin
|
||||
@@ -152,6 +122,92 @@ GINKGO_FOCUS='[It] should create a new Runner resource from the specified templa
|
||||
go test -v -run TestAPIs github.com/actions-runner-controller/actions-runner-controller/controllers
|
||||
```
|
||||
|
||||
#### Helm Version Bumps
|
||||
### Running End to End Tests
|
||||
|
||||
In general we ask you not to bump the version in your PR, the maintainers in general manage the publishing of a new chart.
|
||||
> **Notes for Ubuntu 20.04+ users**
|
||||
>
|
||||
> If you're using Ubuntu 20.04 or greater, you might have installed `docker` with `snap`.
|
||||
>
|
||||
> If you want to stick with `snap`-provided `docker`, do not forget to set `TMPDIR` to somewhere under `$HOME`.
|
||||
> Otherwise `kind load docker-image` fail while running `docker save`.
|
||||
> See https://kind.sigs.k8s.io/docs/user/known-issues/#docker-installed-with-snap for more information.
|
||||
|
||||
To test your local changes against both PAT and App based authentication please run the `acceptance` make target with the authentication configuration details provided:
|
||||
|
||||
```shell
|
||||
# This sets `VERSION` envvar to some appropriate value
|
||||
. hack/make-env.sh
|
||||
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
APP_ID=*** \
|
||||
PRIVATE_KEY_FILE_PATH=path/to/pem/file \
|
||||
INSTALLATION_ID=*** \
|
||||
make acceptance
|
||||
```
|
||||
|
||||
#### Rerunning a failed test
|
||||
|
||||
When one of tests run by `make acceptance` failed, you'd probably like to rerun only the failed one.
|
||||
|
||||
It can be done by `make acceptance/run` and by setting the combination of `ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm|kubectl` and `ACCEPTANCE_TEST_SECRET_TYPE=token|app` values that failed (note, you just need to set the corresponding authentication configuration in this circumstance)
|
||||
|
||||
In the example below, we rerun the test for the combination `ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm ACCEPTANCE_TEST_SECRET_TYPE=token` only:
|
||||
|
||||
```shell
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
ACCEPTANCE_TEST_DEPLOYMENT_TOOL=helm \
|
||||
ACCEPTANCE_TEST_SECRET_TYPE=token \
|
||||
make acceptance/run
|
||||
```
|
||||
|
||||
#### Testing in a non-kind cluster
|
||||
|
||||
If you prefer to test in a non-kind cluster, you can instead run:
|
||||
|
||||
```shell
|
||||
KUBECONFIG=path/to/kubeconfig \
|
||||
DOCKER_USER=*** \
|
||||
GITHUB_TOKEN=*** \
|
||||
APP_ID=*** \
|
||||
PRIVATE_KEY_FILE_PATH=path/to/pem/file \
|
||||
INSTALLATION_ID=*** \
|
||||
ACCEPTANCE_TEST_SECRET_TYPE=token \
|
||||
make docker-build acceptance/setup \
|
||||
acceptance/deploy \
|
||||
acceptance/tests
|
||||
```
|
||||
|
||||
### Code conventions
|
||||
|
||||
Before shipping your PR, please check the following items to make sure CI passes.
|
||||
|
||||
- Run `go mod tidy` if you made changes to dependencies.
|
||||
- Format the code using `gofmt`
|
||||
- Run the `golangci-lint` tool locally.
|
||||
- We recommend you use `make lint` to run the tool using a Docker container matching the CI version.
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
Send PR, add issue number to description
|
||||
|
||||
## Helm Version Changes
|
||||
|
||||
In general we ask you not to bump the version in your PR.
|
||||
The maintainers will manage releases and publishing new charts.
|
||||
|
||||
## Testing Controller Built from a Pull Request
|
||||
|
||||
We always appreciate your help in testing open pull requests by deploying custom builds of actions-runner-controller onto your own environment, so that we are extra sure we didn't break anything.
|
||||
|
||||
It is especially true when the pull request is about GitHub Enterprise, both GHEC and GHES, as [maintainers don't have GitHub Enterprise environments for testing](docs/detailed-docs.md#github-enterprise-support).
|
||||
|
||||
The process would look like the below:
|
||||
|
||||
- Clone this repository locally
|
||||
- Checkout the branch. If you use the `gh` command, run `gh pr checkout $PR_NUMBER`
|
||||
- Run `NAME=$DOCKER_USER/actions-runner-controller VERSION=canary make docker-build docker-push` for a custom container image build
|
||||
- Update your actions-runner-controller's controller-manager deployment to use the new image, `$DOCKER_USER/actions-runner-controller:canary`
|
||||
|
||||
Please also note that you need to replace `$DOCKER_USER` with your own DockerHub account name.
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,11 +1,10 @@
|
||||
# Build the manager binary
|
||||
FROM --platform=$BUILDPLATFORM golang:1.18.2 as builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.19.2 as builder
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
# Make it runnable on a distroless image/without libc
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
# Copy the Go Modules manifests
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
@@ -25,20 +24,20 @@ RUN go mod download
|
||||
# With the above commmand,
|
||||
# TARGETOS can be "linux", TARGETARCH can be "amd64", "arm64", and "arm", TARGETVARIANT can be "v7".
|
||||
|
||||
ARG TARGETPLATFORM TARGETOS TARGETARCH TARGETVARIANT
|
||||
ARG TARGETPLATFORM TARGETOS TARGETARCH TARGETVARIANT VERSION=dev
|
||||
|
||||
# We intentionally avoid `--mount=type=cache,mode=0777,target=/go/pkg/mod` in the `go mod download` and the `go build` runs
|
||||
# to avoid https://github.com/moby/buildkit/issues/2334
|
||||
# We can use docker layer cache so the build is fast enogh anyway
|
||||
# We also use per-platform GOCACHE for the same reason.
|
||||
env GOCACHE /build/${TARGETPLATFORM}/root/.cache/go-build
|
||||
ENV GOCACHE /build/${TARGETPLATFORM}/root/.cache/go-build
|
||||
|
||||
# Build
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,mode=0777,target=${GOCACHE} \
|
||||
export GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} && \
|
||||
go build -o /out/manager main.go && \
|
||||
go build -o /out/github-webhook-server ./cmd/githubwebhookserver
|
||||
go build -trimpath -ldflags="-s -w -X 'github.com/actions-runner-controller/actions-runner-controller/build.Version=${VERSION}'" -o /out/manager main.go && \
|
||||
go build -trimpath -ldflags="-s -w" -o /out/github-webhook-server ./cmd/githubwebhookserver
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
@@ -49,6 +48,6 @@ WORKDIR /
|
||||
COPY --from=builder /out/manager .
|
||||
COPY --from=builder /out/github-webhook-server .
|
||||
|
||||
USER nonroot:nonroot
|
||||
USER 65532:65532
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
|
||||
39
Makefile
39
Makefile
@@ -4,8 +4,8 @@ else
|
||||
NAME ?= summerwind/actions-runner-controller
|
||||
endif
|
||||
DOCKER_USER ?= $(shell echo ${NAME} | cut -d / -f1)
|
||||
VERSION ?= latest
|
||||
RUNNER_VERSION ?= 2.292.0
|
||||
VERSION ?= dev
|
||||
RUNNER_VERSION ?= 2.298.2
|
||||
TARGETPLATFORM ?= $(shell arch)
|
||||
RUNNER_NAME ?= ${DOCKER_USER}/actions-runner
|
||||
RUNNER_TAG ?= ${VERSION}
|
||||
@@ -19,6 +19,7 @@ KUBECONTEXT ?= kind-acceptance
|
||||
CLUSTER ?= acceptance
|
||||
CERT_MANAGER_VERSION ?= v1.1.1
|
||||
KUBE_RBAC_PROXY_VERSION ?= v0.11.0
|
||||
SHELLCHECK_VERSION ?= 0.8.0
|
||||
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:generateEmbeddedObjectMeta=true"
|
||||
@@ -31,6 +32,7 @@ GOBIN=$(shell go env GOBIN)
|
||||
endif
|
||||
|
||||
TEST_ASSETS=$(PWD)/test-assets
|
||||
TOOLS_PATH=$(PWD)/.tools
|
||||
|
||||
# default list of platforms for which multiarch image is built
|
||||
ifeq (${PLATFORMS}, )
|
||||
@@ -51,10 +53,13 @@ endif
|
||||
|
||||
all: manager
|
||||
|
||||
lint:
|
||||
docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.49.0 golangci-lint run
|
||||
|
||||
GO_TEST_ARGS ?= -short
|
||||
|
||||
# Run tests
|
||||
test: generate fmt vet manifests
|
||||
test: generate fmt vet manifests shellcheck
|
||||
go test $(GO_TEST_ARGS) ./... -coverprofile cover.out
|
||||
go test -fuzz=Fuzz -fuzztime=10s -run=Fuzz* ./controllers
|
||||
|
||||
@@ -92,7 +97,7 @@ manifests: manifests-gen-crds chart-crds
|
||||
manifests-gen-crds: controller-gen yq
|
||||
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||
for YAMLFILE in config/crd/bases/actions*.yaml; do \
|
||||
$(YQ) write --inplace "$$YAMLFILE" spec.preserveUnknownFields false; \
|
||||
$(YQ) '.spec.preserveUnknownFields = false' --inplace "$$YAMLFILE" ; \
|
||||
done
|
||||
|
||||
chart-crds:
|
||||
@@ -110,6 +115,10 @@ vet:
|
||||
generate: controller-gen
|
||||
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..."
|
||||
|
||||
# Run shellcheck on runner scripts
|
||||
shellcheck: shellcheck-install
|
||||
$(TOOLS_PATH)/shellcheck --shell bash --source-path runner runner/*.bash runner/*.sh
|
||||
|
||||
docker-buildx:
|
||||
export DOCKER_CLI_EXPERIMENTAL=enabled ;\
|
||||
export DOCKER_BUILDKIT=1
|
||||
@@ -119,6 +128,7 @@ docker-buildx:
|
||||
docker buildx build --platform ${PLATFORMS} \
|
||||
--build-arg RUNNER_VERSION=${RUNNER_VERSION} \
|
||||
--build-arg DOCKER_VERSION=${DOCKER_VERSION} \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
-t "${NAME}:${VERSION}" \
|
||||
-f Dockerfile \
|
||||
. ${PUSH_ARG}
|
||||
@@ -242,12 +252,31 @@ ifeq (, $(wildcard $(GOBIN)/yq))
|
||||
YQ_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$YQ_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go install github.com/mikefarah/yq/v3@3.4.0 ;\
|
||||
go install github.com/mikefarah/yq/v4@v4.25.3 ;\
|
||||
rm -rf $$YQ_TMP_DIR ;\
|
||||
}
|
||||
endif
|
||||
YQ=$(GOBIN)/yq
|
||||
|
||||
# find or download shellcheck
|
||||
# download shellcheck if necessary
|
||||
shellcheck-install:
|
||||
ifeq (, $(wildcard $(TOOLS_PATH)/shellcheck))
|
||||
echo "Downloading shellcheck"
|
||||
@{ \
|
||||
set -e ;\
|
||||
SHELLCHECK_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$SHELLCHECK_TMP_DIR ;\
|
||||
curl -LO https://github.com/koalaman/shellcheck/releases/download/v$(SHELLCHECK_VERSION)/shellcheck-v$(SHELLCHECK_VERSION).linux.x86_64.tar.xz ;\
|
||||
tar Jxvf shellcheck-v$(SHELLCHECK_VERSION).linux.x86_64.tar.xz ;\
|
||||
cd $(CURDIR) ;\
|
||||
mkdir -p $(TOOLS_PATH) ;\
|
||||
mv $$SHELLCHECK_TMP_DIR/shellcheck-v$(SHELLCHECK_VERSION)/shellcheck $(TOOLS_PATH)/ ;\
|
||||
rm -rf $$SHELLCHECK_TMP_DIR ;\
|
||||
}
|
||||
endif
|
||||
SHELLCHECK=$(TOOLS_PATH)/shellcheck
|
||||
|
||||
OS_NAME := $(shell uname -s | tr A-Z a-z)
|
||||
|
||||
# find or download etcd
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
* [Tools](#tools)
|
||||
* [Installation](#installation)
|
||||
* [InternalError when calling webhook: context deadline exceeded](#internalerror-when-calling-webhook-context-deadline-exceeded)
|
||||
* [Invalid header field value](#invalid-header-field-value)
|
||||
* [Deployment fails on GKE due to webhooks](#deployment-fails-on-gke-due-to-webhooks)
|
||||
* [Helm chart install failure: certificate signed by unknown authority](#helm-chart-install-failure-certificate-signed-by-unknown-authority)
|
||||
* [Operations](#operations)
|
||||
* [Stuck runner kind or backing pod](#stuck-runner-kind-or-backing-pod)
|
||||
* [Delay in jobs being allocated to runners](#delay-in-jobs-being-allocated-to-runners)
|
||||
@@ -22,39 +23,37 @@ A list of tools which are helpful for troubleshooting
|
||||
|
||||
Troubeshooting runbooks that relate to ARC installation problems
|
||||
|
||||
### Invalid header field value
|
||||
### InternalError when calling webhook: context deadline exceeded
|
||||
|
||||
**Problem**
|
||||
|
||||
```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"
|
||||
}
|
||||
This issue can come up for various reasons like leftovers from previous installations or not being able to access the K8s service's clusterIP associated with the admission webhook server (of ARC).
|
||||
|
||||
```
|
||||
Internal error occurred: failed calling webhook "mutate.runnerdeployment.actions.summerwind.dev":
|
||||
Post "https://actions-runner-controller-webhook.actions-runner-system.svc:443/mutate-actions-summerwind-dev-v1alpha1-runnerdeployment?timeout=10s": context deadline exceeded
|
||||
```
|
||||
|
||||
**Solution**
|
||||
|
||||
Your base64'ed PAT token has a new line at the end, it needs to be created without a `\n` added, either:
|
||||
* `echo -n $TOKEN | base64`
|
||||
* Create the secret as described in the docs using the shell and documented flags
|
||||
First we will try the common solution of checking webhook leftovers from previous installations:
|
||||
|
||||
1. ```bash
|
||||
kubectl get validatingwebhookconfiguration -A
|
||||
kubectl get mutatingwebhookconfiguration -A
|
||||
```
|
||||
2. If you see any webhooks related to actions-runner-controller, delete them:
|
||||
```bash
|
||||
kubectl delete mutatingwebhookconfiguration actions-runner-controller-mutating-webhook-configuration
|
||||
kubectl delete validatingwebhookconfiguration actions-runner-controller-validating-webhook-configuration
|
||||
```
|
||||
|
||||
If that didn't work then probably your K8s control-plane is somehow unable to access the K8s service's clusterIP associated with the admission webhook server:
|
||||
1. You're running apiserver as a binary and you didn't make service cluster IPs available to the host network.
|
||||
2. You're running the apiserver in the pod but your pod network (i.e. CNI plugin installation and config) is not good so your pods(like kube-apiserver) in the K8s control-plane nodes can't access ARC's admission webhook server pod(s) in probably data-plane nodes.
|
||||
|
||||
|
||||
### Deployment fails on GKE due to webhooks
|
||||
|
||||
**Problem**
|
||||
|
||||
Due to GKEs firewall settings you may run into the following errors when trying to deploy runners on a private GKE cluster:
|
||||
|
||||
```
|
||||
Internal error occurred: failed calling webhook "mutate.runner.actions.summerwind.dev":
|
||||
Post https://webhook-service.actions-runner-system.svc:443/mutate-actions-summerwind-dev-v1alpha1-runner?timeout=10s:
|
||||
context deadline exceeded
|
||||
```
|
||||
|
||||
**Solution**<br />
|
||||
Another reason could be due to GKEs firewall settings you may run into the following errors when trying to deploy runners on a private GKE cluster:
|
||||
|
||||
To fix this, you may either:
|
||||
|
||||
@@ -88,6 +87,57 @@ To fix this, you may either:
|
||||
gcloud compute firewall-rules create k8s-cert-manager --source-ranges $SOURCE --target-tags $WORKER_NODES_TAG --allow TCP:9443 --network $NETWORK
|
||||
```
|
||||
|
||||
### Invalid header field value
|
||||
|
||||
**Problem**
|
||||
|
||||
```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"
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**
|
||||
|
||||
Your base64'ed PAT token has a new line at the end, it needs to be created without a `\n` added, either:
|
||||
* `echo -n $TOKEN | base64`
|
||||
* Create the secret as described in the docs using the shell and documented flags
|
||||
|
||||
### Helm chart install failure: certificate signed by unknown authority
|
||||
|
||||
**Problem**
|
||||
|
||||
```
|
||||
Error: UPGRADE FAILED: failed to create resource: Internal error occurred: failed calling webhook "webhook.cert-manager.io": failed to call webhook: Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority
|
||||
```
|
||||
|
||||
Apparently, it's failing while `helm` is creating one of resources defined in the ARC chart and the cause was that cert-manager's webhook is not working correctly, due to the missing or the invalid CA certficate.
|
||||
|
||||
You'd try to tail logs from the `cert-manager-cainjector` and see it's failing with an error like:
|
||||
|
||||
```
|
||||
$ kubectl -n cert-manager logs cert-manager-cainjector-7cdbb9c945-g6bt4
|
||||
I0703 03:31:55.159339 1 start.go:91] "starting" version="v1.1.1" revision="3ac7418070e22c87fae4b22603a6b952f797ae96"
|
||||
I0703 03:31:55.615061 1 leaderelection.go:243] attempting to acquire leader lease kube-system/cert-manager-cainjector-leader-election...
|
||||
I0703 03:32:10.738039 1 leaderelection.go:253] successfully acquired lease kube-system/cert-manager-cainjector-leader-election
|
||||
I0703 03:32:10.739941 1 recorder.go:52] cert-manager/controller-runtime/manager/events "msg"="Normal" "message"="cert-manager-cainjector-7cdbb9c945-g6bt4_88e4bc70-eded-4343-a6fb-0ddd6434eb55 became leader" "object"={"kind":"ConfigMap","namespace":"kube-system","name":"cert-manager-cainjector-leader-election","uid":"942a021e-364c-461a-978c-f54a95723cdc","apiVersion":"v1","resourceVersion":"1576"} "reason"="LeaderElection"
|
||||
E0703 03:32:11.192128 1 start.go:119] cert-manager/ca-injector "msg"="manager goroutine exited" "error"=null
|
||||
I0703 03:32:12.339197 1 request.go:645] Throttling request took 1.047437675s, request: GET:https://10.96.0.1:443/apis/storage.k8s.io/v1beta1?timeout=32s
|
||||
E0703 03:32:13.143790 1 start.go:151] cert-manager/ca-injector "msg"="Error registering certificate based controllers. Retrying after 5 seconds." "error"="no matches for kind \"MutatingWebhookConfiguration\" in version \"admissionregistration.k8s.io/v1beta1\""
|
||||
Error: error registering secret controller: no matches for kind "MutatingWebhookConfiguration" in version "admissionregistration.k8s.io/v1beta1"
|
||||
```
|
||||
|
||||
**Solution**
|
||||
|
||||
Your cluster is based on a new enough Kubernetes of version 1.22 or greater which does not support the legacy `admissionregistration.k8s.io/v1beta1` API anymore, and your `cert-manager` is not up-to-date hence it's still trying to use the leagcy Kubernetes API.
|
||||
|
||||
In many cases, it's not an option to downgrade Kubernetes. So, just upgrade `cert-manager` to a more recent version that does have have the support for the specific Kubernetes version you're using.
|
||||
|
||||
See https://cert-manager.io/docs/installation/supported-releases/ for the list of available cert-manager versions.
|
||||
|
||||
## Operations
|
||||
|
||||
Troubeshooting runbooks that relate to ARC operational problems
|
||||
@@ -117,7 +167,7 @@ are in a namespace not shared with anything else_
|
||||
|
||||
**Problem**
|
||||
|
||||
ARC isn't involved in jobs actually getting allocated to a runner. ARC is responsible for orchestrating runners and the runner lifecycle. Why some people see large delays in job allocation is not clear however it has been https://github.com/actions-runner-controller/actions-runner-controller/issues/1387#issuecomment-1122593984 that this is caused from the self-update process somehow.
|
||||
ARC isn't involved in jobs actually getting allocated to a runner. ARC is responsible for orchestrating runners and the runner lifecycle. Why some people see large delays in job allocation is not clear however it has been confirmed https://github.com/actions-runner-controller/actions-runner-controller/issues/1387#issuecomment-1122593984 that this is caused from the self-update process somehow.
|
||||
|
||||
**Solution**
|
||||
|
||||
@@ -206,8 +256,28 @@ spec:
|
||||
env: []
|
||||
```
|
||||
|
||||
There may be more places you need to tweak for MTU.
|
||||
Please consult issues like #651 for more information.
|
||||
If the issue still persists, you can set the `ARC_DOCKER_MTU_PROPAGATION` to propagate the host MTU to networks created
|
||||
by the GitHub Runner. For instance:
|
||||
|
||||
```yaml
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: github-runner
|
||||
namespace: github-system
|
||||
spec:
|
||||
replicas: 6
|
||||
template:
|
||||
spec:
|
||||
dockerMTU: 1400
|
||||
repository: $username/$repo
|
||||
env:
|
||||
- name: ARC_DOCKER_MTU_PROPAGATION
|
||||
value: "true"
|
||||
```
|
||||
|
||||
You can read the discussion regarding this issue in
|
||||
(#1406)[https://github.com/actions-runner-controller/actions-runner-controller/issues/1046].
|
||||
|
||||
## Unable to scale to zero with TotalNumberOfQueuedAndInProgressWorkflowRuns
|
||||
|
||||
|
||||
97
acceptance/argotunnel.sh
Executable file
97
acceptance/argotunnel.sh
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# See https://developers.cloudflare.com/cloudflare-one/tutorials/many-cfd-one-tunnel/
|
||||
|
||||
kubectl create ns tunnel || :
|
||||
|
||||
kubectl -n tunnel delete secret tunnel-credentials || :
|
||||
|
||||
kubectl -n tunnel create secret generic tunnel-credentials \
|
||||
--from-file=credentials.json=$HOME/.cloudflared/${TUNNEL_ID}.json || :
|
||||
|
||||
cat <<MANIFEST | kubectl -n tunnel ${OP} -f -
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cloudflared
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cloudflared
|
||||
replicas: 2 # You could also consider elastic scaling for this deployment
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cloudflared
|
||||
spec:
|
||||
containers:
|
||||
- name: cloudflared
|
||||
image: cloudflare/cloudflared:latest
|
||||
args:
|
||||
- tunnel
|
||||
# Points cloudflared to the config file, which configures what
|
||||
# cloudflared will actually do. This file is created by a ConfigMap
|
||||
# below.
|
||||
- --config
|
||||
- /etc/cloudflared/config/config.yaml
|
||||
- run
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
# Cloudflared has a /ready endpoint which returns 200 if and only if
|
||||
# it has an active connection to the edge.
|
||||
path: /ready
|
||||
port: 2000
|
||||
failureThreshold: 1
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/cloudflared/config
|
||||
readOnly: true
|
||||
# Each tunnel has an associated "credentials file" which authorizes machines
|
||||
# to run the tunnel. cloudflared will read this file from its local filesystem,
|
||||
# and it'll be stored in a k8s secret.
|
||||
- name: creds
|
||||
mountPath: /etc/cloudflared/creds
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: creds
|
||||
secret:
|
||||
secretName: tunnel-credentials
|
||||
# Create a config.yaml file from the ConfigMap below.
|
||||
- name: config
|
||||
configMap:
|
||||
name: cloudflared
|
||||
items:
|
||||
- key: config.yaml
|
||||
path: config.yaml
|
||||
---
|
||||
# This ConfigMap is just a way to define the cloudflared config.yaml file in k8s.
|
||||
# It's useful to define it in k8s, rather than as a stand-alone .yaml file, because
|
||||
# this lets you use various k8s templating solutions (e.g. Helm charts) to
|
||||
# parameterize your config, instead of just using string literals.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cloudflared
|
||||
data:
|
||||
config.yaml: |
|
||||
# Name of the tunnel you want to run
|
||||
tunnel: ${TUNNEL_NAME}
|
||||
credentials-file: /etc/cloudflared/creds/credentials.json
|
||||
# Serves the metrics server under /metrics and the readiness server under /ready
|
||||
metrics: 0.0.0.0:2000
|
||||
# Autoupdates applied in a k8s pod will be lost when the pod is removed or restarted, so
|
||||
# autoupdate doesn't make sense in Kubernetes. However, outside of Kubernetes, we strongly
|
||||
# recommend using autoupdate.
|
||||
no-autoupdate: true
|
||||
ingress:
|
||||
# The first rule proxies traffic to the httpbin sample Service defined in app.yaml
|
||||
- hostname: ${TUNNEL_HOSTNAME}
|
||||
service: http://actions-runner-controller-github-webhook-server.actions-runner-system:80
|
||||
# This rule matches any traffic which didn't match a previous rule, and responds with HTTP 404.
|
||||
- service: http_status:404
|
||||
MANIFEST
|
||||
|
||||
kubectl -n tunnel delete po -l app=cloudflared || :
|
||||
@@ -41,8 +41,23 @@ TEST_ID=${TEST_ID:-default}
|
||||
|
||||
if [ "${tool}" == "helm" ]; then
|
||||
set -v
|
||||
|
||||
CHART=${CHART:-charts/actions-runner-controller}
|
||||
|
||||
flags=()
|
||||
if [ "${IMAGE_PULL_SECRET}" != "" ]; then
|
||||
flags+=( --set imagePullSecrets[0].name=${IMAGE_PULL_SECRET})
|
||||
flags+=( --set image.actionsRunnerImagePullSecrets[0].name=${IMAGE_PULL_SECRET})
|
||||
flags+=( --set githubWebhookServer.imagePullSecrets[0].name=${IMAGE_PULL_SECRET})
|
||||
fi
|
||||
if [ "${CHART_VERSION}" != "" ]; then
|
||||
flags+=( --version ${CHART_VERSION})
|
||||
fi
|
||||
|
||||
set -vx
|
||||
|
||||
helm upgrade --install actions-runner-controller \
|
||||
charts/actions-runner-controller \
|
||||
${CHART} \
|
||||
-n actions-runner-system \
|
||||
--create-namespace \
|
||||
--set syncPeriod=${SYNC_PERIOD} \
|
||||
@@ -51,6 +66,7 @@ if [ "${tool}" == "helm" ]; then
|
||||
--set image.tag=${VERSION} \
|
||||
--set podAnnotations.test-id=${TEST_ID} \
|
||||
--set githubWebhookServer.podAnnotations.test-id=${TEST_ID} \
|
||||
${flags[@]} --set image.imagePullPolicy=${IMAGE_PULL_POLICY} \
|
||||
-f ${VALUES_FILE}
|
||||
set +v
|
||||
# To prevent `CustomResourceDefinition.apiextensions.k8s.io "runners.actions.summerwind.dev" is invalid: metadata.annotations: Too long: must have at most 262144 bytes`
|
||||
@@ -76,56 +92,3 @@ kubectl -n actions-runner-system wait deploy/actions-runner-controller --for con
|
||||
|
||||
# Adhocly wait for some time until actions-runner-controller's admission webhook gets ready
|
||||
sleep 20
|
||||
|
||||
RUNNER_LABEL=${RUNNER_LABEL:-self-hosted}
|
||||
|
||||
if [ -n "${TEST_REPO}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_ORG= RUNNER_MIN_REPLICAS=${REPO_RUNNER_MIN_REPLICAS} NAME=repo-runnerset envsubst | kubectl apply -f -
|
||||
else
|
||||
echo 'Deploying runnerdeployment and hra. Set USE_RUNNERSET if you want to deploy runnerset instead.'
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_ORG= RUNNER_MIN_REPLICAS=${REPO_RUNNER_MIN_REPLICAS} NAME=repo-runnerdeploy envsubst | kubectl apply -f -
|
||||
fi
|
||||
else
|
||||
echo 'Skipped deploying runnerdeployment and hra. Set TEST_REPO to "yourorg/yourrepo" to deploy.'
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ORG}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} NAME=org-runnerset envsubst | kubectl apply -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} NAME=org-runnerdeploy envsubst | kubectl apply -f -
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ORG_GROUP}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ORG_GROUP} NAME=orggroup-runnerset envsubst | kubectl apply -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ORG_GROUP} NAME=orggroup-runnerdeploy envsubst | kubectl apply -f -
|
||||
fi
|
||||
else
|
||||
echo 'Skipped deploying enterprise runnerdeployment. Set TEST_ORG_GROUP to deploy.'
|
||||
fi
|
||||
else
|
||||
echo 'Skipped deploying organizational runnerdeployment. Set TEST_ORG to deploy.'
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ENTERPRISE}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} NAME=enterprise-runnerset envsubst | kubectl apply -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} NAME=enterprise-runnerdeploy envsubst | kubectl apply -f -
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ENTERPRISE_GROUP}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ENTERPRISE_GROUP} NAME=enterprisegroup-runnerset envsubst | kubectl apply -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ENTERPRISE_GROUP} NAME=enterprisegroup-runnerdeploy envsubst | kubectl apply -f -
|
||||
fi
|
||||
else
|
||||
echo 'Skipped deploying enterprise runnerdeployment. Set TEST_ENTERPRISE_GROUP to deploy.'
|
||||
fi
|
||||
else
|
||||
echo 'Skipped deploying enterprise runnerdeployment. Set TEST_ENTERPRISE to deploy.'
|
||||
fi
|
||||
|
||||
60
acceptance/deploy_runners.sh
Executable file
60
acceptance/deploy_runners.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
OP=${OP:-apply}
|
||||
|
||||
RUNNER_LABEL=${RUNNER_LABEL:-self-hosted}
|
||||
|
||||
cat acceptance/testdata/kubernetes_container_mode.envsubst.yaml | NAMESPACE=${RUNNER_NAMESPACE} envsubst | kubectl apply -f -
|
||||
|
||||
if [ -n "${TEST_REPO}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_ORG= RUNNER_MIN_REPLICAS=${REPO_RUNNER_MIN_REPLICAS} NAME=repo-runnerset envsubst | kubectl ${OP} -f -
|
||||
else
|
||||
echo "Running ${OP} runnerdeployment and hra. Set USE_RUNNERSET if you want to deploy runnerset instead."
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_ORG= RUNNER_MIN_REPLICAS=${REPO_RUNNER_MIN_REPLICAS} NAME=repo-runnerdeploy envsubst | kubectl ${OP} -f -
|
||||
fi
|
||||
else
|
||||
echo "Skipped ${OP} for runnerdeployment and hra. Set TEST_REPO to "yourorg/yourrepo" to deploy."
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ORG}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} NAME=org-runnerset envsubst | kubectl ${OP} -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} NAME=org-runnerdeploy envsubst | kubectl ${OP} -f -
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ORG_GROUP}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ORG_GROUP} NAME=orggroup-runnerset envsubst | kubectl ${OP} -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ENTERPRISE= TEST_REPO= RUNNER_MIN_REPLICAS=${ORG_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ORG_GROUP} NAME=orggroup-runnerdeploy envsubst | kubectl ${OP} -f -
|
||||
fi
|
||||
else
|
||||
echo "Skipped ${OP} on enterprise runnerdeployment. Set TEST_ORG_GROUP to ${OP}."
|
||||
fi
|
||||
else
|
||||
echo "Skipped ${OP} on organizational runnerdeployment. Set TEST_ORG to ${OP}."
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ENTERPRISE}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} NAME=enterprise-runnerset envsubst | kubectl ${OP} -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} NAME=enterprise-runnerdeploy envsubst | kubectl ${OP} -f -
|
||||
fi
|
||||
|
||||
if [ -n "${TEST_ENTERPRISE_GROUP}" ]; then
|
||||
if [ "${USE_RUNNERSET}" != "false" ]; then
|
||||
cat acceptance/testdata/runnerset.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ENTERPRISE_GROUP} NAME=enterprisegroup-runnerset envsubst | kubectl ${OP} -f -
|
||||
else
|
||||
cat acceptance/testdata/runnerdeploy.envsubst.yaml | TEST_ORG= TEST_REPO= RUNNER_MIN_REPLICAS=${ENTERPRISE_RUNNER_MIN_REPLICAS} TEST_GROUP=${TEST_ENTERPRISE_GROUP} NAME=enterprisegroup-runnerdeploy envsubst | kubectl ${OP} -f -
|
||||
fi
|
||||
else
|
||||
echo "Skipped ${OP} on enterprise runnerdeployment. Set TEST_ENTERPRISE_GROUP to ${OP}."
|
||||
fi
|
||||
else
|
||||
echo "Skipped ${OP} on enterprise runnerdeployment. Set TEST_ENTERPRISE to ${OP}."
|
||||
fi
|
||||
86
acceptance/testdata/kubernetes_container_mode.envsubst.yaml
vendored
Normal file
86
acceptance/testdata/kubernetes_container_mode.envsubst.yaml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# USAGE:
|
||||
# cat acceptance/testdata/kubernetes_container_mode.envsubst.yaml | NAMESPACE=default envsubst | kubectl apply -f -
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: k8s-mode-runner
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "create", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/exec"]
|
||||
verbs: ["get", "create"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/log"]
|
||||
verbs: ["get", "list", "watch",]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "list", "create", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "create", "delete"]
|
||||
# Needed to report test success by crating a cm from within workflow job step
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
verbs: ["create", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: runner-status-updater
|
||||
rules:
|
||||
- apiGroups: ["actions.summerwind.dev"]
|
||||
resources: ["runners/status"]
|
||||
verbs: ["get", "update", "patch"]
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ${RUNNER_SERVICE_ACCOUNT_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
---
|
||||
# To verify it's working, try:
|
||||
# kubectl auth can-i --as system:serviceaccount:default:runner get pod
|
||||
# If incomplete, workflows and jobs would fail with an error message like:
|
||||
# Error: Error: The Service account needs the following permissions [{"group":"","verbs":["get","list","create","delete"],"resource":"pods","subresource":""},{"group":"","verbs":["get","create"],"resource":"pods","subresource":"exec"},{"group":"","verbs":["get","list","watch"],"resource":"pods","subresource":"log"},{"group":"batch","verbs":["get","list","create","delete"],"resource":"jobs","subresource":""},{"group":"","verbs":["create","delete","get","list"],"resource":"secrets","subresource":""}] on the pod resource in the 'default' namespace. Please contact your self hosted runner administrator.
|
||||
# Error: Process completed with exit code 1.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
# This role binding allows "jane" to read pods in the "default" namespace.
|
||||
# You need to already have a Role named "pod-reader" in that namespace.
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: runner-k8s-mode-runner
|
||||
namespace: ${NAMESPACE}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: ${RUNNER_SERVICE_ACCOUNT_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: k8s-mode-runner
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: runner-runner-stat-supdater
|
||||
namespace: ${NAMESPACE}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: ${RUNNER_SERVICE_ACCOUNT_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: runner-status-updater
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: org-runnerdeploy-runner-work-dir
|
||||
labels:
|
||||
content: org-runnerdeploy-runner-work-dir
|
||||
provisioner: rancher.io/local-path
|
||||
reclaimPolicy: Delete
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
30
acceptance/testdata/runnerdeploy.envsubst.yaml
vendored
30
acceptance/testdata/runnerdeploy.envsubst.yaml
vendored
@@ -1,3 +1,13 @@
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: ${NAME}-runner-work-dir
|
||||
labels:
|
||||
content: ${NAME}-runner-work-dir
|
||||
provisioner: rancher.io/local-path
|
||||
reclaimPolicy: Delete
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
---
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
@@ -39,10 +49,30 @@ spec:
|
||||
labels:
|
||||
- "${RUNNER_LABEL}"
|
||||
|
||||
env:
|
||||
- name: ROLLING_UPDATE_PHASE
|
||||
value: "${ROLLING_UPDATE_PHASE}"
|
||||
- name: ARC_DOCKER_MTU_PROPAGATION
|
||||
value: "true"
|
||||
|
||||
dockerMTU: 1400
|
||||
|
||||
#
|
||||
# Non-standard working directory
|
||||
#
|
||||
# workDir: "/"
|
||||
|
||||
# # Uncomment the below to enable the kubernetes container mode
|
||||
# # See https://github.com/actions-runner-controller/actions-runner-controller#runner-with-k8s-jobs
|
||||
containerMode: ${RUNNER_CONTAINER_MODE}
|
||||
workVolumeClaimTemplate:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: "${NAME}-runner-work-dir"
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
serviceAccountName: ${RUNNER_SERVICE_ACCOUNT_NAME}
|
||||
---
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: HorizontalRunnerAutoscaler
|
||||
|
||||
63
acceptance/testdata/runnerset.envsubst.yaml
vendored
63
acceptance/testdata/runnerset.envsubst.yaml
vendored
@@ -112,6 +112,7 @@ spec:
|
||||
labels:
|
||||
app: ${NAME}
|
||||
spec:
|
||||
serviceAccountName: ${RUNNER_SERVICE_ACCOUNT_NAME}
|
||||
containers:
|
||||
- name: runner
|
||||
imagePullPolicy: IfNotPresent
|
||||
@@ -120,10 +121,14 @@ spec:
|
||||
value: "${RUNNER_FEATURE_FLAG_EPHEMERAL}"
|
||||
- name: GOMODCACHE
|
||||
value: "/home/runner/.cache/go-mod"
|
||||
- name: ROLLING_UPDATE_PHASE
|
||||
value: "${ROLLING_UPDATE_PHASE}"
|
||||
# PV-backed runner work dir
|
||||
volumeMounts:
|
||||
- name: work
|
||||
mountPath: /runner/_work
|
||||
# Comment out the ephemeral work volume if you're going to test the kubernetes container mode
|
||||
# The volume and mount with the same names will be created by workVolumeClaimTemplate and the kubernetes container mode support.
|
||||
# - name: work
|
||||
# mountPath: /runner/_work
|
||||
# Cache docker image layers, in case dockerdWithinRunnerContainer=true
|
||||
- name: var-lib-docker
|
||||
mountPath: /var/lib/docker
|
||||
@@ -150,30 +155,31 @@ spec:
|
||||
# https://github.com/actions/setup-go/blob/56a61c9834b4a4950dbbf4740af0b8a98c73b768/src/installer.ts#L144
|
||||
mountPath: "/opt/hostedtoolcache"
|
||||
# Valid only when dockerdWithinRunnerContainer=false
|
||||
- name: docker
|
||||
# PV-backed runner work dir
|
||||
volumeMounts:
|
||||
- name: work
|
||||
mountPath: /runner/_work
|
||||
# Cache docker image layers, in case dockerdWithinRunnerContainer=false
|
||||
- name: var-lib-docker
|
||||
mountPath: /var/lib/docker
|
||||
# image: mumoshu/actions-runner-dind:dev
|
||||
# - name: docker
|
||||
# # PV-backed runner work dir
|
||||
# volumeMounts:
|
||||
# - name: work
|
||||
# mountPath: /runner/_work
|
||||
# # Cache docker image layers, in case dockerdWithinRunnerContainer=false
|
||||
# - name: var-lib-docker
|
||||
# mountPath: /var/lib/docker
|
||||
# # image: mumoshu/actions-runner-dind:dev
|
||||
|
||||
# For buildx cache
|
||||
- name: cache
|
||||
mountPath: "/home/runner/.cache"
|
||||
volumes:
|
||||
- name: work
|
||||
ephemeral:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: "${NAME}-runner-work-dir"
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
# # For buildx cache
|
||||
# - name: cache
|
||||
# mountPath: "/home/runner/.cache"
|
||||
# Comment out the ephemeral work volume if you're going to test the kubernetes container mode
|
||||
# volumes:
|
||||
# - name: work
|
||||
# ephemeral:
|
||||
# volumeClaimTemplate:
|
||||
# spec:
|
||||
# accessModes:
|
||||
# - ReadWriteOnce
|
||||
# storageClassName: "${NAME}-runner-work-dir"
|
||||
# resources:
|
||||
# requests:
|
||||
# storage: 10Gi
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: vol1
|
||||
@@ -251,3 +257,10 @@ spec:
|
||||
minReplicas: ${RUNNER_MIN_REPLICAS}
|
||||
maxReplicas: 10
|
||||
scaleDownDelaySecondsAfterScaleOut: ${RUNNER_SCALE_DOWN_DELAY_SECONDS_AFTER_SCALE_OUT}
|
||||
# Comment out the whole metrics if you'd like to solely test webhook-based scaling
|
||||
metrics:
|
||||
- type: PercentageRunnersBusy
|
||||
scaleUpThreshold: '0.75'
|
||||
scaleDownThreshold: '0.25'
|
||||
scaleUpFactor: '2'
|
||||
scaleDownFactor: '0.5'
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
# Set actions-runner-controller settings for testing
|
||||
logLevel: "-4"
|
||||
imagePullSecrets: []
|
||||
image:
|
||||
# This needs to be an empty array rather than a single-item array with empty name.
|
||||
# Otherwise you end up with the following error on helm-upgrade:
|
||||
# Error: UPGRADE FAILED: failed to create patch: map: map[] does not contain declared merge key: name && failed to create patch: map: map[] does not contain declared merge key: name
|
||||
actionsRunnerImagePullSecrets: []
|
||||
runner:
|
||||
statusUpdateHook:
|
||||
enabled: true
|
||||
rbac:
|
||||
allowGrantingKubernetesContainerModePermissions: true
|
||||
githubWebhookServer:
|
||||
imagePullSecrets: []
|
||||
logLevel: "-4"
|
||||
enabled: true
|
||||
labels: {}
|
||||
|
||||
18
adrs/0000-TEMPLATE.md
Normal file
18
adrs/0000-TEMPLATE.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Title
|
||||
|
||||
<!-- ADR titles should typically be imperative sentences. -->
|
||||
|
||||
**Status**: (Proposed|Accepted|Rejected|Superceded|Deprecated)
|
||||
|
||||
## Context
|
||||
|
||||
*What is the issue or background knowledge necessary for future readers
|
||||
to understand why this ADR was written?*
|
||||
|
||||
## Decision
|
||||
|
||||
**What** is the change being proposed? / **How** will it be implemented?*
|
||||
|
||||
## Consequences
|
||||
|
||||
*What becomes easier or more difficult to do because of this change?*
|
||||
@@ -60,6 +60,9 @@ type HorizontalRunnerAutoscalerSpec struct {
|
||||
// The earlier a scheduled override is, the higher it is prioritized.
|
||||
// +optional
|
||||
ScheduledOverrides []ScheduledOverride `json:"scheduledOverrides,omitempty"`
|
||||
|
||||
// +optional
|
||||
GitHubAPICredentialsFrom *GitHubAPICredentialsFrom `json:"githubAPICredentialsFrom,omitempty"`
|
||||
}
|
||||
|
||||
type ScaleUpTrigger struct {
|
||||
@@ -130,7 +133,7 @@ type ScaleTargetRef struct {
|
||||
|
||||
type MetricSpec struct {
|
||||
// Type is the type of metric to be used for autoscaling.
|
||||
// The only supported Type is TotalNumberOfQueuedAndInProgressWorkflowRuns
|
||||
// It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
// RepositoryNames is the list of repository names to be used for calculating the metric.
|
||||
@@ -170,7 +173,7 @@ type MetricSpec struct {
|
||||
}
|
||||
|
||||
// ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule.
|
||||
// A schedule can optionally be recurring, so that the correspoding override happens every day, week, month, or year.
|
||||
// A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||
type ScheduledOverride struct {
|
||||
// StartTime is the time at which the first override starts.
|
||||
StartTime metav1.Time `json:"startTime"`
|
||||
|
||||
@@ -18,8 +18,10 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -71,6 +73,19 @@ type RunnerConfig struct {
|
||||
VolumeSizeLimit *resource.Quantity `json:"volumeSizeLimit,omitempty"`
|
||||
// +optional
|
||||
VolumeStorageMedium *string `json:"volumeStorageMedium,omitempty"`
|
||||
|
||||
// +optional
|
||||
ContainerMode string `json:"containerMode,omitempty"`
|
||||
|
||||
GitHubAPICredentialsFrom *GitHubAPICredentialsFrom `json:"githubAPICredentialsFrom,omitempty"`
|
||||
}
|
||||
|
||||
type GitHubAPICredentialsFrom struct {
|
||||
SecretRef SecretReference `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
||||
type SecretReference struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RunnerPodSpec defines the desired pod spec fields of the runner pod
|
||||
@@ -135,6 +150,9 @@ type RunnerPodSpec struct {
|
||||
// +optional
|
||||
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
|
||||
|
||||
// +optional
|
||||
PriorityClassName string `json:"priorityClassName,omitempty"`
|
||||
|
||||
// +optional
|
||||
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"`
|
||||
|
||||
@@ -152,12 +170,37 @@ type RunnerPodSpec struct {
|
||||
// +optional
|
||||
RuntimeClassName *string `json:"runtimeClassName,omitempty"`
|
||||
|
||||
// +optional
|
||||
DnsPolicy corev1.DNSPolicy `json:"dnsPolicy,omitempty"`
|
||||
|
||||
// +optional
|
||||
DnsConfig *corev1.PodDNSConfig `json:"dnsConfig,omitempty"`
|
||||
|
||||
// +optional
|
||||
WorkVolumeClaimTemplate *WorkVolumeClaimTemplate `json:"workVolumeClaimTemplate,omitempty"`
|
||||
}
|
||||
|
||||
func (rs *RunnerSpec) Validate(rootPath *field.Path) field.ErrorList {
|
||||
var (
|
||||
errList field.ErrorList
|
||||
err error
|
||||
)
|
||||
|
||||
err = rs.validateRepository()
|
||||
if err != nil {
|
||||
errList = append(errList, field.Invalid(rootPath.Child("repository"), rs.Repository, err.Error()))
|
||||
}
|
||||
|
||||
err = rs.validateWorkVolumeClaimTemplate()
|
||||
if err != nil {
|
||||
errList = append(errList, field.Invalid(rootPath.Child("workVolumeClaimTemplate"), rs.WorkVolumeClaimTemplate, err.Error()))
|
||||
}
|
||||
|
||||
return errList
|
||||
}
|
||||
|
||||
// ValidateRepository validates repository field.
|
||||
func (rs *RunnerSpec) ValidateRepository() error {
|
||||
func (rs *RunnerSpec) validateRepository() error {
|
||||
// Enterprise, Organization and repository are both exclusive.
|
||||
foundCount := 0
|
||||
if len(rs.Organization) > 0 {
|
||||
@@ -179,6 +222,18 @@ func (rs *RunnerSpec) ValidateRepository() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RunnerSpec) validateWorkVolumeClaimTemplate() error {
|
||||
if rs.ContainerMode != "kubernetes" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rs.WorkVolumeClaimTemplate == nil {
|
||||
return errors.New("Spec.ContainerMode: kubernetes must have workVolumeClaimTemplate field specified")
|
||||
}
|
||||
|
||||
return rs.WorkVolumeClaimTemplate.validate()
|
||||
}
|
||||
|
||||
// RunnerStatus defines the observed state of Runner
|
||||
type RunnerStatus struct {
|
||||
// Turns true only if the runner pod is ready.
|
||||
@@ -207,13 +262,60 @@ type RunnerStatusRegistration struct {
|
||||
ExpiresAt metav1.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type WorkVolumeClaimTemplate struct {
|
||||
StorageClassName string `json:"storageClassName"`
|
||||
AccessModes []corev1.PersistentVolumeAccessMode `json:"accessModes"`
|
||||
Resources corev1.ResourceRequirements `json:"resources"`
|
||||
}
|
||||
|
||||
func (w *WorkVolumeClaimTemplate) validate() error {
|
||||
if w.AccessModes == nil || len(w.AccessModes) == 0 {
|
||||
return errors.New("Access mode should have at least one mode specified")
|
||||
}
|
||||
|
||||
for _, accessMode := range w.AccessModes {
|
||||
switch accessMode {
|
||||
case corev1.ReadWriteOnce, corev1.ReadWriteMany:
|
||||
default:
|
||||
return fmt.Errorf("Access mode %v is not supported", accessMode)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WorkVolumeClaimTemplate) V1Volume() corev1.Volume {
|
||||
return corev1.Volume{
|
||||
Name: "work",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Ephemeral: &corev1.EphemeralVolumeSource{
|
||||
VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: w.AccessModes,
|
||||
StorageClassName: &w.StorageClassName,
|
||||
Resources: w.Resources,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WorkVolumeClaimTemplate) V1VolumeMount(mountPath string) corev1.VolumeMount {
|
||||
return corev1.VolumeMount{
|
||||
MountPath: mountPath,
|
||||
Name: "work",
|
||||
}
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +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.repository",name=Repository,type=string
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.group",name=Group,type=string
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string
|
||||
// +kubebuilder:printcolumn:JSONPath=".status.phase",name=Status,type=string
|
||||
// +kubebuilder:printcolumn:JSONPath=".status.message",name=Message,type=string
|
||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||
|
||||
// Runner is the Schema for the runners API
|
||||
@@ -235,11 +337,7 @@ func (r Runner) IsRegisterable() bool {
|
||||
}
|
||||
|
||||
now := metav1.Now()
|
||||
if r.Status.Registration.ExpiresAt.Before(&now) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return !r.Status.Registration.ExpiresAt.Before(&now)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
@@ -66,15 +66,7 @@ func (r *Runner) ValidateDelete() error {
|
||||
|
||||
// Validate validates resource spec.
|
||||
func (r *Runner) Validate() error {
|
||||
var (
|
||||
errList field.ErrorList
|
||||
err error
|
||||
)
|
||||
|
||||
err = r.Spec.ValidateRepository()
|
||||
if err != nil {
|
||||
errList = append(errList, field.Invalid(field.NewPath("spec", "repository"), r.Spec.Repository, err.Error()))
|
||||
}
|
||||
errList := r.Spec.Validate(field.NewPath("spec"))
|
||||
|
||||
if len(errList) > 0 {
|
||||
return apierrors.NewInvalid(r.GroupVersionKind().GroupKind(), r.Name, errList)
|
||||
|
||||
@@ -33,7 +33,7 @@ type RunnerDeploymentSpec struct {
|
||||
|
||||
// EffectiveTime is the time the upstream controller requested to sync Replicas.
|
||||
// It is usually populated by the webhook-based autoscaler via HRA.
|
||||
// The value is inherited to RunnerRepicaSet(s) and used to prevent ephemeral runners from unnecessarily recreated.
|
||||
// The value is inherited to RunnerReplicaSet(s) and used to prevent ephemeral runners from unnecessarily recreated.
|
||||
//
|
||||
// +optional
|
||||
// +nullable
|
||||
|
||||
@@ -66,15 +66,7 @@ func (r *RunnerDeployment) ValidateDelete() error {
|
||||
|
||||
// Validate validates resource spec.
|
||||
func (r *RunnerDeployment) Validate() error {
|
||||
var (
|
||||
errList field.ErrorList
|
||||
err error
|
||||
)
|
||||
|
||||
err = r.Spec.Template.Spec.ValidateRepository()
|
||||
if err != nil {
|
||||
errList = append(errList, field.Invalid(field.NewPath("spec", "template", "spec", "repository"), r.Spec.Template.Spec.Repository, err.Error()))
|
||||
}
|
||||
errList := r.Spec.Template.Spec.Validate(field.NewPath("spec", "template", "spec"))
|
||||
|
||||
if len(errList) > 0 {
|
||||
return apierrors.NewInvalid(r.GroupVersionKind().GroupKind(), r.Name, errList)
|
||||
|
||||
@@ -66,15 +66,7 @@ func (r *RunnerReplicaSet) ValidateDelete() error {
|
||||
|
||||
// Validate validates resource spec.
|
||||
func (r *RunnerReplicaSet) Validate() error {
|
||||
var (
|
||||
errList field.ErrorList
|
||||
err error
|
||||
)
|
||||
|
||||
err = r.Spec.Template.Spec.ValidateRepository()
|
||||
if err != nil {
|
||||
errList = append(errList, field.Invalid(field.NewPath("spec", "template", "spec", "repository"), r.Spec.Template.Spec.Repository, err.Error()))
|
||||
}
|
||||
errList := r.Spec.Template.Spec.Validate(field.NewPath("spec", "template", "spec"))
|
||||
|
||||
if len(errList) > 0 {
|
||||
return apierrors.NewInvalid(r.GroupVersionKind().GroupKind(), r.Name, errList)
|
||||
|
||||
@@ -33,6 +33,12 @@ type RunnerSetSpec struct {
|
||||
// +nullable
|
||||
EffectiveTime *metav1.Time `json:"effectiveTime,omitempty"`
|
||||
|
||||
// +optional
|
||||
ServiceAccountName string `json:"serviceAccountName,omitempty"`
|
||||
|
||||
// +optional
|
||||
WorkVolumeClaimTemplate *WorkVolumeClaimTemplate `json:"workVolumeClaimTemplate,omitempty"`
|
||||
|
||||
appsv1.StatefulSetSpec `json:",inline"`
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,22 @@ func (in *CheckRunSpec) DeepCopy() *CheckRunSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitHubAPICredentialsFrom) DeepCopyInto(out *GitHubAPICredentialsFrom) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubAPICredentialsFrom.
|
||||
func (in *GitHubAPICredentialsFrom) DeepCopy() *GitHubAPICredentialsFrom {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GitHubAPICredentialsFrom)
|
||||
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
|
||||
@@ -231,6 +247,11 @@ func (in *HorizontalRunnerAutoscalerSpec) DeepCopyInto(out *HorizontalRunnerAuto
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.GitHubAPICredentialsFrom != nil {
|
||||
in, out := &in.GitHubAPICredentialsFrom, &out.GitHubAPICredentialsFrom
|
||||
*out = new(GitHubAPICredentialsFrom)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerSpec.
|
||||
@@ -425,6 +446,11 @@ func (in *RunnerConfig) DeepCopyInto(out *RunnerConfig) {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.GitHubAPICredentialsFrom != nil {
|
||||
in, out := &in.GitHubAPICredentialsFrom, &out.GitHubAPICredentialsFrom
|
||||
*out = new(GitHubAPICredentialsFrom)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerConfig.
|
||||
@@ -741,6 +767,11 @@ func (in *RunnerPodSpec) DeepCopyInto(out *RunnerPodSpec) {
|
||||
*out = new(v1.PodDNSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.WorkVolumeClaimTemplate != nil {
|
||||
in, out := &in.WorkVolumeClaimTemplate, &out.WorkVolumeClaimTemplate
|
||||
*out = new(WorkVolumeClaimTemplate)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerPodSpec.
|
||||
@@ -939,6 +970,11 @@ func (in *RunnerSetSpec) DeepCopyInto(out *RunnerSetSpec) {
|
||||
in, out := &in.EffectiveTime, &out.EffectiveTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.WorkVolumeClaimTemplate != nil {
|
||||
in, out := &in.WorkVolumeClaimTemplate, &out.WorkVolumeClaimTemplate
|
||||
*out = new(WorkVolumeClaimTemplate)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.StatefulSetSpec.DeepCopyInto(&out.StatefulSetSpec)
|
||||
}
|
||||
|
||||
@@ -1126,6 +1162,42 @@ func (in *ScheduledOverride) DeepCopy() *ScheduledOverride {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretReference) DeepCopyInto(out *SecretReference) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference.
|
||||
func (in *SecretReference) DeepCopy() *SecretReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkVolumeClaimTemplate) DeepCopyInto(out *WorkVolumeClaimTemplate) {
|
||||
*out = *in
|
||||
if in.AccessModes != nil {
|
||||
in, out := &in.AccessModes, &out.AccessModes
|
||||
*out = make([]v1.PersistentVolumeAccessMode, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.Resources.DeepCopyInto(&out.Resources)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkVolumeClaimTemplate.
|
||||
func (in *WorkVolumeClaimTemplate) DeepCopy() *WorkVolumeClaimTemplate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WorkVolumeClaimTemplate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WorkflowJobSpec) DeepCopyInto(out *WorkflowJobSpec) {
|
||||
*out = *in
|
||||
|
||||
4
build/version.go
Normal file
4
build/version.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package build
|
||||
|
||||
// This is overridden at build-time using go-build ldflags. dev is the fallback value
|
||||
var Version = "NA"
|
||||
@@ -15,10 +15,10 @@ type: application
|
||||
# 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.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.18.0
|
||||
version: 0.21.1
|
||||
|
||||
# Used as the default manager tag value when no tag property is provided in the values.yaml
|
||||
appVersion: 0.23.0
|
||||
appVersion: 0.26.0
|
||||
|
||||
home: https://github.com/actions-runner-controller/actions-runner-controller
|
||||
|
||||
|
||||
@@ -8,104 +8,105 @@ All additional docs are kept in the `docs/` folder, this README is solely for do
|
||||
|
||||
> _Default values are the defaults set in the charts `values.yaml`, some properties have default configurations in the code for when the property is omitted or invalid_
|
||||
|
||||
| Key | Description | Default |
|
||||
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
|
||||
| `labels` | Set labels to apply to all resources in the chart | |
|
||||
| `replicaCount` | Set the number of controller pods | 1 |
|
||||
| `webhookPort` | Set the containerPort for the webhook Pod | 9443 |
|
||||
| `syncPeriod` | Set the period in which the controler reconciles the desired runners count | 10m |
|
||||
| `enableLeaderElection` | Enable election configuration | true |
|
||||
| `leaderElectionId` | Set the election ID for the controller group | |
|
||||
| `githubEnterpriseServerURL` | Set the URL for a self-hosted GitHub Enterprise Server | |
|
||||
| `githubURL` | Override GitHub URL to be used for GitHub API calls | |
|
||||
| `githubUploadURL` | Override GitHub Upload URL to be used for GitHub API calls | |
|
||||
| `runnerGithubURL` | Override GitHub URL to be used by runners during registration | |
|
||||
| `logLevel` | Set the log level of the controller container | |
|
||||
| `additionalVolumes` | Set additional volumes to add to the manager container | |
|
||||
| `additionalVolumeMounts` | Set additional volume mounts to add to the manager container | |
|
||||
| `authSecret.create` | Deploy the controller auth secret | false |
|
||||
| `authSecret.name` | Set the name of the auth secret | controller-manager |
|
||||
| `authSecret.annotations` | Set annotations for the auth Secret | |
|
||||
| `authSecret.github_app_id` | The ID of your GitHub App. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_app_installation_id` | The ID of your GitHub App installation. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_app_private_key` | The multiline string of your GitHub App's private key. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_token` | Your chosen GitHub PAT token. **This can't be set at the same time as the `authSecret.github_app_*`** | |
|
||||
| `authSecret.github_basicauth_username` | Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
|
||||
| `authSecret.github_basicauth_password` | Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
|
||||
| `dockerRegistryMirror` | The default Docker Registry Mirror used by runners. | |
|
||||
| `hostNetwork` | The "hostNetwork" of the controller container | false |
|
||||
| `image.repository` | The "repository/image" of the controller container | summerwind/actions-runner-controller |
|
||||
| `image.tag` | The tag of the controller container | |
|
||||
| `image.actionsRunnerRepositoryAndTag` | The "repository/image" of the actions runner container | summerwind/actions-runner:latest |
|
||||
| `image.actionsRunnerImagePullSecrets` | Optional image pull secrets to be included in the runner pod's ImagePullSecrets | |
|
||||
| `image.dindSidecarRepositoryAndTag` | The "repository/image" of the dind sidecar container | docker:dind |
|
||||
| `image.pullPolicy` | The pull policy of the controller image | IfNotPresent |
|
||||
| `metrics.serviceMonitor` | Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false |
|
||||
| `metrics.serviceAnnotations` | Set annotations for the provisioned metrics service resource | |
|
||||
| `metrics.port` | Set port of metrics service | 8443 |
|
||||
| `metrics.proxy.enabled` | Deploy kube-rbac-proxy container in controller pod | true |
|
||||
| `metrics.proxy.image.repository` | The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy |
|
||||
| `metrics.proxy.image.tag` | The tag of the kube-proxy image to use when pulling the container | v0.10.0 |
|
||||
| `metrics.serviceMonitorLabels` | Set labels to apply to ServiceMonitor resources | |
|
||||
| `imagePullSecrets` | Specifies the secret to be used when pulling the controller pod containers | |
|
||||
| `fullnameOverride` | Override the full resource names | |
|
||||
| `nameOverride` | Override the resource name prefix | |
|
||||
| `serviceAccount.annotations` | Set annotations to the service account | |
|
||||
| `serviceAccount.create` | Deploy the controller pod under a service account | true |
|
||||
| `podAnnotations` | Set annotations for the controller pod | |
|
||||
| `podLabels` | Set labels for the controller pod | |
|
||||
| `serviceAccount.name` | Set the name of the service account | |
|
||||
| `securityContext` | Set the security context for each container in the controller pod | |
|
||||
| `podSecurityContext` | Set the security context to controller pod | |
|
||||
| `service.annotations` | Set annotations for the provisioned webhook service resource | |
|
||||
| `service.port` | Set controller service ports | |
|
||||
| `service.type` | Set controller service type | |
|
||||
| `topologySpreadConstraints` | Set the controller pod topologySpreadConstraints | |
|
||||
| `nodeSelector` | Set the controller pod nodeSelector | |
|
||||
| `resources` | Set the controller pod resources | |
|
||||
| `affinity` | Set the controller pod affinity rules | |
|
||||
| `podDisruptionBudget.enabled` | Enables a PDB to ensure HA of controller pods | false |
|
||||
| `podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | |
|
||||
| `podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
|
||||
| `tolerations` | Set the controller pod tolerations | |
|
||||
| `env` | Set environment variables for the controller container | |
|
||||
| `priorityClassName` | Set the controller pod priorityClassName | |
|
||||
| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). |
|
||||
| `scope.singleNamespace` | Limit the controller to watch a single namespace | false |
|
||||
| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true |
|
||||
| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | |
|
||||
| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | |
|
||||
| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 |
|
||||
| `githubWebhookServer.useRunnerGroupsVisibility` | Enable supporting runner groups with custom visibility. This will incur in extra API calls and may blow up your budget. Currently, you also need to set `githubWebhookServer.secret.enabled` to enable this feature. | false |
|
||||
| `githubWebhookServer.syncPeriod` | Set the period in which the controller reconciles the resources | 10m |
|
||||
| `githubWebhookServer.enabled` | Deploy the webhook server pod | false |
|
||||
| `githubWebhookServer.secret.enabled` | Passes the webhook hook secret to the github-webhook-server | false |
|
||||
| `githubWebhookServer.secret.create` | Deploy the webhook hook secret | false |
|
||||
| `githubWebhookServer.secret.name` | Set the name of the webhook hook secret | github-webhook-server |
|
||||
| `githubWebhookServer.secret.github_webhook_secret_token` | Set the webhook secret token value | |
|
||||
| `githubWebhookServer.imagePullSecrets` | Specifies the secret to be used when pulling the githubWebhookServer pod containers | |
|
||||
| `githubWebhookServer.nameOverride` | Override the resource name prefix | |
|
||||
| `githubWebhookServer.fullnameOverride` | Override the full resource names | |
|
||||
| `githubWebhookServer.serviceAccount.create` | Deploy the githubWebhookServer under a service account | true |
|
||||
| `githubWebhookServer.serviceAccount.annotations` | Set annotations for the service account | |
|
||||
| `githubWebhookServer.serviceAccount.name` | Set the service account name | |
|
||||
| `githubWebhookServer.podAnnotations` | Set annotations for the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.podLabels` | Set labels for the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.podSecurityContext` | Set the security context to githubWebhookServer pod | |
|
||||
| `githubWebhookServer.securityContext` | Set the security context for each container in the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.resources` | Set the githubWebhookServer pod resources | |
|
||||
| `githubWebhookServer.topologySpreadConstraints` | Set the githubWebhookServer pod topologySpreadConstraints | |
|
||||
| `githubWebhookServer.nodeSelector` | Set the githubWebhookServer pod nodeSelector | |
|
||||
| `githubWebhookServer.tolerations` | Set the githubWebhookServer pod tolerations | |
|
||||
| `githubWebhookServer.affinity` | Set the githubWebhookServer pod affinity rules | |
|
||||
| `githubWebhookServer.priorityClassName` | Set the githubWebhookServer pod priorityClassName | |
|
||||
| `githubWebhookServer.service.type` | Set githubWebhookServer service type | |
|
||||
| `githubWebhookServer.service.ports` | Set githubWebhookServer service ports | `[{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}]` |
|
||||
| `githubWebhookServer.ingress.enabled` | Deploy an ingress kind for the githubWebhookServer | false |
|
||||
| `githubWebhookServer.ingress.annotations` | Set annotations for the ingress kind | |
|
||||
| `githubWebhookServer.ingress.hosts` | Set hosts configuration for ingress | `[{"host": "chart-example.local", "paths": []}]` |
|
||||
| `githubWebhookServer.ingress.tls` | Set tls configuration for ingress | |
|
||||
| `githubWebhookServer.ingress.ingressClassName` | Set ingress class name | |
|
||||
| `githubWebhookServer.podDisruptionBudget.enabled` | Enables a PDB to ensure HA of githubwebhook pods | false |
|
||||
| `githubWebhookServer.podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | |
|
||||
| `githubWebhookServer.podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
|
||||
| Key | Description | Default |
|
||||
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
|
||||
| `labels` | Set labels to apply to all resources in the chart | |
|
||||
| `replicaCount` | Set the number of controller pods | 1 |
|
||||
| `webhookPort` | Set the containerPort for the webhook Pod | 9443 |
|
||||
| `syncPeriod` | Set the period in which the controller reconciles the desired runners count | 1m |
|
||||
| `enableLeaderElection` | Enable election configuration | true |
|
||||
| `leaderElectionId` | Set the election ID for the controller group | |
|
||||
| `githubEnterpriseServerURL` | Set the URL for a self-hosted GitHub Enterprise Server | |
|
||||
| `githubURL` | Override GitHub URL to be used for GitHub API calls | |
|
||||
| `githubUploadURL` | Override GitHub Upload URL to be used for GitHub API calls | |
|
||||
| `runnerGithubURL` | Override GitHub URL to be used by runners during registration | |
|
||||
| `logLevel` | Set the log level of the controller container | |
|
||||
| `additionalVolumes` | Set additional volumes to add to the manager container | |
|
||||
| `additionalVolumeMounts` | Set additional volume mounts to add to the manager container | |
|
||||
| `authSecret.create` | Deploy the controller auth secret | false |
|
||||
| `authSecret.name` | Set the name of the auth secret | controller-manager |
|
||||
| `authSecret.annotations` | Set annotations for the auth Secret | |
|
||||
| `authSecret.github_app_id` | The ID of your GitHub App. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_app_installation_id` | The ID of your GitHub App installation. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_app_private_key` | The multiline string of your GitHub App's private key. **This can't be set at the same time as `authSecret.github_token`** | |
|
||||
| `authSecret.github_token` | Your chosen GitHub PAT token. **This can't be set at the same time as the `authSecret.github_app_*`** | |
|
||||
| `authSecret.github_basicauth_username` | Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
|
||||
| `authSecret.github_basicauth_password` | Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
|
||||
| `dockerRegistryMirror` | The default Docker Registry Mirror used by runners. | |
|
||||
| `hostNetwork` | The "hostNetwork" of the controller container | false |
|
||||
| `image.repository` | The "repository/image" of the controller container | summerwind/actions-runner-controller |
|
||||
| `image.tag` | The tag of the controller container | |
|
||||
| `image.actionsRunnerRepositoryAndTag` | The "repository/image" of the actions runner container | summerwind/actions-runner:latest |
|
||||
| `image.actionsRunnerImagePullSecrets` | Optional image pull secrets to be included in the runner pod's ImagePullSecrets | |
|
||||
| `image.dindSidecarRepositoryAndTag` | The "repository/image" of the dind sidecar container | docker:dind |
|
||||
| `image.pullPolicy` | The pull policy of the controller image | IfNotPresent |
|
||||
| `metrics.serviceMonitor` | Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false |
|
||||
| `metrics.serviceAnnotations` | Set annotations for the provisioned metrics service resource | |
|
||||
| `metrics.port` | Set port of metrics service | 8443 |
|
||||
| `metrics.proxy.enabled` | Deploy kube-rbac-proxy container in controller pod | true |
|
||||
| `metrics.proxy.image.repository` | The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy |
|
||||
| `metrics.proxy.image.tag` | The tag of the kube-proxy image to use when pulling the container | v0.10.0 |
|
||||
| `metrics.serviceMonitorLabels` | Set labels to apply to ServiceMonitor resources | |
|
||||
| `imagePullSecrets` | Specifies the secret to be used when pulling the controller pod containers | |
|
||||
| `fullnameOverride` | Override the full resource names | |
|
||||
| `nameOverride` | Override the resource name prefix | |
|
||||
| `serviceAccount.annotations` | Set annotations to the service account | |
|
||||
| `serviceAccount.create` | Deploy the controller pod under a service account | true |
|
||||
| `podAnnotations` | Set annotations for the controller pod | |
|
||||
| `podLabels` | Set labels for the controller pod | |
|
||||
| `serviceAccount.name` | Set the name of the service account | |
|
||||
| `securityContext` | Set the security context for each container in the controller pod | |
|
||||
| `podSecurityContext` | Set the security context to controller pod | |
|
||||
| `service.annotations` | Set annotations for the provisioned webhook service resource | |
|
||||
| `service.port` | Set controller service ports | |
|
||||
| `service.type` | Set controller service type | |
|
||||
| `topologySpreadConstraints` | Set the controller pod topologySpreadConstraints | |
|
||||
| `nodeSelector` | Set the controller pod nodeSelector | |
|
||||
| `resources` | Set the controller pod resources | |
|
||||
| `affinity` | Set the controller pod affinity rules | |
|
||||
| `podDisruptionBudget.enabled` | Enables a PDB to ensure HA of controller pods | false |
|
||||
| `podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | |
|
||||
| `podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
|
||||
| `tolerations` | Set the controller pod tolerations | |
|
||||
| `env` | Set environment variables for the controller container | |
|
||||
| `priorityClassName` | Set the controller pod priorityClassName | |
|
||||
| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). |
|
||||
| `scope.singleNamespace` | Limit the controller to watch a single namespace | false |
|
||||
| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true |
|
||||
| `runner.statusUpdateHook.enabled` | Use custom RBAC for runners (role, role binding and service account), this will enable reporting runner statuses | false |
|
||||
| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | |
|
||||
| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | |
|
||||
| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 |
|
||||
| `githubWebhookServer.useRunnerGroupsVisibility` | Enable supporting runner groups with custom visibility. This will incur in extra API calls and may blow up your budget. Currently, you also need to set `githubWebhookServer.secret.enabled` to enable this feature. | false |
|
||||
| `githubWebhookServer.enabled` | Deploy the webhook server pod | false |
|
||||
| `githubWebhookServer.queueLimit` | Set the queue size limit in the githubWebhookServer | |
|
||||
| `githubWebhookServer.secret.enabled` | Passes the webhook hook secret to the github-webhook-server | false |
|
||||
| `githubWebhookServer.secret.create` | Deploy the webhook hook secret | false |
|
||||
| `githubWebhookServer.secret.name` | Set the name of the webhook hook secret | github-webhook-server |
|
||||
| `githubWebhookServer.secret.github_webhook_secret_token` | Set the webhook secret token value | |
|
||||
| `githubWebhookServer.imagePullSecrets` | Specifies the secret to be used when pulling the githubWebhookServer pod containers | |
|
||||
| `githubWebhookServer.nameOverride` | Override the resource name prefix | |
|
||||
| `githubWebhookServer.fullnameOverride` | Override the full resource names | |
|
||||
| `githubWebhookServer.serviceAccount.create` | Deploy the githubWebhookServer under a service account | true |
|
||||
| `githubWebhookServer.serviceAccount.annotations` | Set annotations for the service account | |
|
||||
| `githubWebhookServer.serviceAccount.name` | Set the service account name | |
|
||||
| `githubWebhookServer.podAnnotations` | Set annotations for the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.podLabels` | Set labels for the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.podSecurityContext` | Set the security context to githubWebhookServer pod | |
|
||||
| `githubWebhookServer.securityContext` | Set the security context for each container in the githubWebhookServer pod | |
|
||||
| `githubWebhookServer.resources` | Set the githubWebhookServer pod resources | |
|
||||
| `githubWebhookServer.topologySpreadConstraints` | Set the githubWebhookServer pod topologySpreadConstraints | |
|
||||
| `githubWebhookServer.nodeSelector` | Set the githubWebhookServer pod nodeSelector | |
|
||||
| `githubWebhookServer.tolerations` | Set the githubWebhookServer pod tolerations | |
|
||||
| `githubWebhookServer.affinity` | Set the githubWebhookServer pod affinity rules | |
|
||||
| `githubWebhookServer.priorityClassName` | Set the githubWebhookServer pod priorityClassName | |
|
||||
| `githubWebhookServer.service.type` | Set githubWebhookServer service type | |
|
||||
| `githubWebhookServer.service.ports` | Set githubWebhookServer service ports | `[{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}]` |
|
||||
| `githubWebhookServer.ingress.enabled` | Deploy an ingress kind for the githubWebhookServer | false |
|
||||
| `githubWebhookServer.ingress.annotations` | Set annotations for the ingress kind | |
|
||||
| `githubWebhookServer.ingress.hosts` | Set hosts configuration for ingress | `[{"host": "chart-example.local", "paths": []}]` |
|
||||
| `githubWebhookServer.ingress.tls` | Set tls configuration for ingress | |
|
||||
| `githubWebhookServer.ingress.ingressClassName` | Set ingress class name | |
|
||||
| `githubWebhookServer.podDisruptionBudget.enabled` | Enables a PDB to ensure HA of githubwebhook pods | false |
|
||||
| `githubWebhookServer.podDisruptionBudget.minAvailable` | Minimum number of pods that must be available after eviction | |
|
||||
| `githubWebhookServer.podDisruptionBudget.maxUnavailable` | Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
|
||||
|
||||
@@ -61,6 +61,16 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
githubAPICredentialsFrom:
|
||||
properties:
|
||||
secretRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
maxReplicas:
|
||||
description: MaxReplicas is the maximum number of replicas the deployment is allowed to scale
|
||||
type: integer
|
||||
@@ -92,7 +102,7 @@ spec:
|
||||
description: ScaleUpThreshold is the percentage of busy runners greater than which will trigger the hpa to scale runners up.
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of metric to be used for autoscaling. The only supported Type is TotalNumberOfQueuedAndInProgressWorkflowRuns
|
||||
description: Type is the type of metric to be used for autoscaling. It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
@@ -170,7 +180,7 @@ spec:
|
||||
scheduledOverrides:
|
||||
description: ScheduledOverrides is the list of ScheduledOverride. It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. The earlier a scheduled override is, the higher it is prioritized.
|
||||
items:
|
||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the correspoding override happens every day, week, month, or year.
|
||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||
properties:
|
||||
endTime:
|
||||
description: EndTime is the time at which the first override ends.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -114,4 +114,4 @@ Create the name of the service account to use
|
||||
|
||||
{{- define "actions-runner-controller.pdbName" -}}
|
||||
{{- include "actions-runner-controller.fullname" . | trunc 59 }}-pdb
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "actions-runner-controller.serviceMonitorName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
endpoints:
|
||||
- path: /metrics
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{- if .Values.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1beta1
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
@@ -58,15 +58,15 @@ spec:
|
||||
{{- if .Values.scope.singleNamespace }}
|
||||
- "--watch-namespace={{ default .Release.Namespace .Values.scope.watchNamespace }}"
|
||||
{{- end }}
|
||||
{{- if .Values.githubAPICacheDuration }}
|
||||
- "--github-api-cache-duration={{ .Values.githubAPICacheDuration }}"
|
||||
{{- end }}
|
||||
{{- if .Values.logLevel }}
|
||||
- "--log-level={{ .Values.logLevel }}"
|
||||
{{- end }}
|
||||
{{- if .Values.runnerGithubURL }}
|
||||
- "--runner-github-url={{ .Values.runnerGithubURL }}"
|
||||
{{- end }}
|
||||
{{- if .Values.runner.statusUpdateHook.enabled }}
|
||||
- "--runner-status-update-hook"
|
||||
{{- end }}
|
||||
command:
|
||||
- "/manager"
|
||||
env:
|
||||
@@ -118,10 +118,14 @@ spec:
|
||||
name: {{ include "actions-runner-controller.secretName" . }}
|
||||
optional: true
|
||||
{{- end }}
|
||||
{{- if kindIs "slice" .Values.env }}
|
||||
{{- toYaml .Values.env | nindent 8 }}
|
||||
{{- else }}
|
||||
{{- range $key, $val := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
|
||||
name: manager
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
|
||||
@@ -39,7 +39,6 @@ spec:
|
||||
{{- $metricsHost := .Values.metrics.proxy.enabled | ternary "127.0.0.1" "0.0.0.0" }}
|
||||
{{- $metricsPort := .Values.metrics.proxy.enabled | ternary "8080" .Values.metrics.port }}
|
||||
- "--metrics-addr={{ $metricsHost }}:{{ $metricsPort }}"
|
||||
- "--sync-period={{ .Values.githubWebhookServer.syncPeriod }}"
|
||||
{{- if .Values.githubWebhookServer.logLevel }}
|
||||
- "--log-level={{ .Values.githubWebhookServer.logLevel }}"
|
||||
{{- end }}
|
||||
@@ -49,6 +48,9 @@ spec:
|
||||
{{- if .Values.runnerGithubURL }}
|
||||
- "--runner-github-url={{ .Values.runnerGithubURL }}"
|
||||
{{- end }}
|
||||
{{- if .Values.githubWebhookServer.queueLimit }}
|
||||
- "--queue-limit={{ .Values.githubWebhookServer.queueLimit }}"
|
||||
{{- end }}
|
||||
command:
|
||||
- "/github-webhook-server"
|
||||
env:
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
{{- if .Values.githubWebhookServer.ingress.enabled -}}
|
||||
{{- $fullName := include "actions-runner-controller-github-webhook-server.fullname" . -}}
|
||||
{{- $svcPort := (index .Values.githubWebhookServer.service.ports 0).port -}}
|
||||
{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
{{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else if .Capabilities.APIVersions.Has "extensions/v1beta1" }}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
@@ -42,19 +36,12 @@ spec:
|
||||
{{- end }}
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
{{- if $.Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
|
||||
pathType: {{ .pathType }}
|
||||
{{- end }}
|
||||
backend:
|
||||
{{- if $.Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
|
||||
service:
|
||||
name: {{ $fullName }}
|
||||
port:
|
||||
number: {{ $svcPort }}
|
||||
{{- else }}
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{- if .Values.githubWebhookServer.podDisruptionBudget.enabled }}
|
||||
apiVersion: policy/v1beta1
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
@@ -12,5 +12,17 @@ data:
|
||||
{{- if .Values.githubWebhookServer.secret.github_webhook_secret_token }}
|
||||
github_webhook_secret_token: {{ .Values.githubWebhookServer.secret.github_webhook_secret_token | toString | b64enc }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubWebhookServer.secret.github_app_id }}
|
||||
github_app_id: {{ .Values.githubWebhookServer.secret.github_app_id | toString | b64enc }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubWebhookServer.secret.github_app_installation_id }}
|
||||
github_app_installation_id: {{ .Values.githubWebhookServer.secret.github_app_installation_id | toString | b64enc }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubWebhookServer.secret.github_app_private_key }}
|
||||
github_app_private_key: {{ .Values.githubWebhookServer.secret.github_app_private_key | toString | b64enc }}
|
||||
{{- end }}
|
||||
{{- if .Values.githubWebhookServer.secret.github_token }}
|
||||
github_token: {{ .Values.githubWebhookServer.secret.github_token | toString | b64enc }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -8,6 +8,7 @@ metadata:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "actions-runner-controller-github-webhook-server.serviceMonitorName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
endpoints:
|
||||
- path: /metrics
|
||||
|
||||
@@ -250,3 +250,72 @@ rules:
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
{{- if .Values.runner.statusUpdateHook.enabled }}
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
{{- end }}
|
||||
{{- if .Values.rbac.allowGrantingKubernetesContainerModePermissions }}
|
||||
{{/* These permissions are required by ARC to create RBAC resources for the runner pod to use the kubernetes container mode. */}}
|
||||
{{/* See https://github.com/actions-runner-controller/actions-runner-controller/pull/1268/files#r917331632 */}}
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/exec
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods/log
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- "batch"
|
||||
resources:
|
||||
- jobs
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- create
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
{{- end }}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
{{/*
|
||||
We will use a self managed CA if one is not provided by cert-manager
|
||||
*/}}
|
||||
{{- $ca := genCA "actions-runner-ca" 3650 }}
|
||||
{{- $cert := genSignedCert (printf "%s.%s.svc" (include "actions-runner-controller.webhookServiceName" .) .Release.Namespace) nil (list (printf "%s.%s.svc" (include "actions-runner-controller.webhookServiceName" .) .Release.Namespace)) 3650 $ca }}
|
||||
---
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: MutatingWebhookConfiguration
|
||||
@@ -20,6 +24,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ quote .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -48,6 +54,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -76,6 +84,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -104,6 +114,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -145,6 +157,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -173,6 +187,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -201,6 +217,8 @@ webhooks:
|
||||
clientConfig:
|
||||
{{- if .Values.admissionWebHooks.caBundle }}
|
||||
caBundle: {{ .Values.admissionWebHooks.caBundle }}
|
||||
{{- else if not .Values.certManagerEnabled }}
|
||||
caBundle: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
service:
|
||||
name: {{ include "actions-runner-controller.webhookServiceName" . }}
|
||||
@@ -219,3 +237,18 @@ webhooks:
|
||||
resources:
|
||||
- runnerreplicasets
|
||||
sideEffects: None
|
||||
{{ if not (or .Values.admissionWebHooks.caBundle .Values.certManagerEnabled) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "actions-runner-controller.servingCertName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "actions-runner-controller.labels" . | nindent 4 }}
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
tls.crt: {{ $cert.Cert | b64enc | quote }}
|
||||
tls.key: {{ $cert.Key | b64enc | quote }}
|
||||
ca.crt: {{ $ca.Cert | b64enc | quote }}
|
||||
{{- end }}
|
||||
|
||||
@@ -15,12 +15,6 @@ enableLeaderElection: true
|
||||
# Must be unique if more than one controller installed onto the same namespace.
|
||||
#leaderElectionId: "actions-runner-controller"
|
||||
|
||||
# DEPRECATED: This has been removed as unnecessary in #1192
|
||||
# The controller tries its best not to repeat the duplicate GitHub API call
|
||||
# within this duration.
|
||||
# Defaults to syncPeriod - 10s.
|
||||
#githubAPICacheDuration: 30s
|
||||
|
||||
# The URL of your GitHub Enterprise server, if you're using one.
|
||||
#githubEnterpriseServerURL: https://github.example.com
|
||||
|
||||
@@ -67,6 +61,18 @@ imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
runner:
|
||||
statusUpdateHook:
|
||||
enabled: false
|
||||
|
||||
rbac:
|
||||
{}
|
||||
# # This allows ARC to dynamically create a ServiceAccount and a Role for each Runner pod that uses "kubernetes" container mode,
|
||||
# # by extending ARC's manager role to have the same permissions required by the pod runs the runner agent in "kubernetes" container mode.
|
||||
# # Without this, Kubernetes blocks ARC to create the role to prevent a priviledge escalation.
|
||||
# # See https://github.com/actions-runner-controller/actions-runner-controller/pull/1268/files#r917327010
|
||||
# allowGrantingKubernetesContainerModePermissions: true
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
@@ -109,7 +115,7 @@ metrics:
|
||||
enabled: true
|
||||
image:
|
||||
repository: quay.io/brancz/kube-rbac-proxy
|
||||
tag: v0.12.0
|
||||
tag: v0.13.1
|
||||
|
||||
resources:
|
||||
{}
|
||||
@@ -143,10 +149,20 @@ priorityClassName: ""
|
||||
|
||||
env:
|
||||
{}
|
||||
# specify additional environment variables for the controller pod.
|
||||
# It's possible to specify either key vale pairs e.g.:
|
||||
# http_proxy: "proxy.com:8080"
|
||||
# https_proxy: "proxy.com:8080"
|
||||
# no_proxy: ""
|
||||
|
||||
# or a list of complete environment variable definitions e.g.:
|
||||
# - name: GITHUB_APP_INSTALLATION_ID
|
||||
# valueFrom:
|
||||
# secretKeyRef:
|
||||
# key: some_key_in_the_secret
|
||||
# name: some-secret-name
|
||||
# optional: true
|
||||
|
||||
## specify additional volumes to mount in the manager container, this can be used
|
||||
## to specify additional storage of material or to inject files from ConfigMaps
|
||||
## into the running container
|
||||
@@ -175,7 +191,6 @@ admissionWebHooks:
|
||||
githubWebhookServer:
|
||||
enabled: false
|
||||
replicaCount: 1
|
||||
syncPeriod: 10m
|
||||
useRunnerGroupsVisibility: false
|
||||
secret:
|
||||
enabled: false
|
||||
@@ -183,6 +198,13 @@ githubWebhookServer:
|
||||
name: "github-webhook-server"
|
||||
### GitHub Webhook Configuration
|
||||
github_webhook_secret_token: ""
|
||||
### GitHub Apps Configuration
|
||||
## NOTE: IDs MUST be strings, use quotes
|
||||
#github_app_id: ""
|
||||
#github_app_installation_id: ""
|
||||
#github_app_private_key: |
|
||||
### GitHub PAT Configuration
|
||||
#github_token: ""
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
@@ -248,3 +270,4 @@ githubWebhookServer:
|
||||
enabled: false
|
||||
# minAvailable: 1
|
||||
# maxUnavailable: 3
|
||||
# queueLimit: 100
|
||||
|
||||
@@ -69,9 +69,8 @@ func main() {
|
||||
|
||||
watchNamespace string
|
||||
|
||||
enableLeaderElection bool
|
||||
syncPeriod time.Duration
|
||||
logLevel string
|
||||
logLevel string
|
||||
queueLimit int
|
||||
|
||||
ghClient *github.Client
|
||||
)
|
||||
@@ -88,10 +87,8 @@ func main() {
|
||||
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.StringVar(&logLevel, "log-level", logging.LogLevelDebug, `The verbosity of the logging. Valid values are "debug", "info", "warn", "error". Defaults to "debug".`)
|
||||
flag.IntVar(&queueLimit, "queue-limit", controllers.DefaultQueueLimit, `The maximum length of the scale operation queue. The scale opration is enqueued per every matching webhook event, and the server returns a 500 HTTP status when the queue was already full on enqueue attempt.`)
|
||||
flag.StringVar(&webhookSecretToken, "github-webhook-secret-token", "", "The personal access token of GitHub.")
|
||||
flag.StringVar(&c.Token, "github-token", c.Token, "The personal access token of GitHub.")
|
||||
flag.Int64Var(&c.AppID, "github-app-id", c.AppID, "The application ID of GitHub App.")
|
||||
@@ -142,10 +139,10 @@ func main() {
|
||||
setupLog.Info("GitHub client is not initialized. Runner groups with custom visibility are not supported. If needed, please provide GitHub authentication. This will incur in extra GitHub API calls")
|
||||
}
|
||||
|
||||
syncPeriod := 10 * time.Minute
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
SyncPeriod: &syncPeriod,
|
||||
LeaderElection: enableLeaderElection,
|
||||
Namespace: watchNamespace,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
Port: 9443,
|
||||
@@ -164,6 +161,7 @@ func main() {
|
||||
SecretKeyBytes: []byte(webhookSecretToken),
|
||||
Namespace: watchNamespace,
|
||||
GitHubClient: ghClient,
|
||||
QueueLimit: queueLimit,
|
||||
}
|
||||
|
||||
if err = hraGitHubWebhook.SetupWithManager(mgr); err != nil {
|
||||
|
||||
@@ -61,6 +61,16 @@ spec:
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
githubAPICredentialsFrom:
|
||||
properties:
|
||||
secretRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
maxReplicas:
|
||||
description: MaxReplicas is the maximum number of replicas the deployment is allowed to scale
|
||||
type: integer
|
||||
@@ -92,7 +102,7 @@ spec:
|
||||
description: ScaleUpThreshold is the percentage of busy runners greater than which will trigger the hpa to scale runners up.
|
||||
type: string
|
||||
type:
|
||||
description: Type is the type of metric to be used for autoscaling. The only supported Type is TotalNumberOfQueuedAndInProgressWorkflowRuns
|
||||
description: Type is the type of metric to be used for autoscaling. It can be TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy.
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
@@ -170,7 +180,7 @@ spec:
|
||||
scheduledOverrides:
|
||||
description: ScheduledOverrides is the list of ScheduledOverride. It can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. The earlier a scheduled override is, the higher it is prioritized.
|
||||
items:
|
||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the correspoding override happens every day, week, month, or year.
|
||||
description: ScheduledOverride can be used to override a few fields of HorizontalRunnerAutoscalerSpec on schedule. A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||
properties:
|
||||
endTime:
|
||||
description: EndTime is the time at which the first override ends.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,6 @@ bases:
|
||||
- ../certmanager
|
||||
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
|
||||
#- ../prometheus
|
||||
# [GH_WEBHOOK_SERVER] To enable the GitHub webhook server, uncomment all sections with 'GH_WEBHOOK_SERVER'.
|
||||
#- ../github-webhook-server
|
||||
|
||||
patchesStrategicMerge:
|
||||
# Protect the /metrics endpoint by putting it behind auth.
|
||||
@@ -46,10 +44,6 @@ patchesStrategicMerge:
|
||||
# 'CERTMANAGER' needs to be enabled to use ca injection
|
||||
- webhookcainjection_patch.yaml
|
||||
|
||||
# [GH_WEBHOOK_SERVER] To enable the GitHub webhook server, uncomment all sections with 'GH_WEBHOOK_SERVER'.
|
||||
# Protect the GitHub webhook server metrics endpoint by putting it behind auth.
|
||||
# - gh-webhook-server-auth-proxy-patch.yaml
|
||||
|
||||
# the following config is for teaching kustomize how to do var substitution
|
||||
vars:
|
||||
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
|
||||
|
||||
@@ -2,11 +2,14 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
images:
|
||||
- name: controller
|
||||
newName: summerwind/actions-runner-controller
|
||||
newTag: latest
|
||||
- name: controller
|
||||
newName: summerwind/actions-runner-controller
|
||||
newTag: latest
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- rbac.yaml
|
||||
- service.yaml
|
||||
- deployment.yaml
|
||||
- rbac.yaml
|
||||
- service.yaml
|
||||
|
||||
patchesStrategicMerge:
|
||||
- gh-webhook-server-auth-proxy-patch.yaml
|
||||
|
||||
@@ -249,3 +249,36 @@ rules:
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- serviceaccounts
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- rolebindings
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- apiGroups:
|
||||
- rbac.authorization.k8s.io
|
||||
resources:
|
||||
- roles
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
|
||||
6
contrib/README.md
Normal file
6
contrib/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
The `contrib` directory is the place for sharing various example code for deploying and operating `actions-runner-controller`.
|
||||
|
||||
Anything contained in this directory is provided as-is. The maintainers of `actions-runner-controller` is not yet commited to provide
|
||||
full support for using, fixing, and enhancing it. However, they will do their best effort to collect feedbacks from early adopters and advanced users like you, and may eventually consider graduating any of the examples as an official addition to the project.
|
||||
|
||||
See https://github.com/actions-runner-controller/actions-runner-controller/pull/1375#issuecomment-1258816470 and https://github.com/actions-runner-controller/actions-runner-controller/pull/1559#issuecomment-1258827496 for more context.
|
||||
25
contrib/examples/actions-runner/.helmignore
Normal file
25
contrib/examples/actions-runner/.helmignore
Normal file
@@ -0,0 +1,25 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
# Docs
|
||||
docs/
|
||||
10
contrib/examples/actions-runner/Chart.yaml
Normal file
10
contrib/examples/actions-runner/Chart.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
apiVersion: v2
|
||||
name: actions-runner
|
||||
description: Helm Chart for Github Actions Runner
|
||||
type: application
|
||||
version: 0.0.1
|
||||
appVersion: 2.290.1
|
||||
|
||||
home: https://github.com/actions-runner-controller/actions-runner-controller/tree/master/runner
|
||||
sources:
|
||||
- https://github.com/actions-runner-controller/actions-runner-controller/tree/master/runner
|
||||
36
contrib/examples/actions-runner/README.md
Normal file
36
contrib/examples/actions-runner/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
## Docs
|
||||
|
||||
All additional docs are kept in the `docs/` folder, this README is solely for documenting the values.yaml keys and values
|
||||
|
||||
## Values
|
||||
|
||||
**_The values are documented as of HEAD, to review the configuration options for your chart version ensure you view this file at the relevent [tag](https://github.com/actions-runner-controller/actions-runner-controller/tags)_**
|
||||
|
||||
> _Default values are the defaults set in the charts values.yaml, some properties have default configurations in the code for when the property is omitted or invalid_
|
||||
|
||||
| Key | Description | Default |
|
||||
|----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
|
||||
| `labels` | Set labels to apply to all resources in the chart | |
|
||||
| `replicaCount` | Set the number of runner pods | 1 |
|
||||
| `image.repository` | The "repository/image" of the runner container | summerwind/actions-runner |
|
||||
| `image.tag` | The tag of the runner container | |
|
||||
| `image.pullPolicy` | The pull policy of the runner image | IfNotPresent |
|
||||
| `imagePullSecrets` | Specifies the secret to be used when pulling the runner pod containers | |
|
||||
| `fullnameOverride` | Override the full resource names | |
|
||||
| `nameOverride` | Override the resource name prefix | |
|
||||
| `podAnnotations` | Set annotations for the runner pod | |
|
||||
| `podLabels` | Set labels for the runner pod | |
|
||||
| `podSecurityContext` | Set the security context to runner pod | |
|
||||
| `nodeSelector` | Set the pod nodeSelector | |
|
||||
| `affinity` | Set the runner pod affinity rules | |
|
||||
| `tolerations` | Set the runner pod tolerations | |
|
||||
| `env` | Set environment variables for the runner container | |
|
||||
| `organization` | Github organization where runner will be registered | test |
|
||||
| `repository` | Github repository where runner will be registered | |
|
||||
| `runnerLabels` | Labels you want to add in your runner | test |
|
||||
| `autoscaler.enabled` | Enable the HorizontalRunnerAutoscaler, if its enabled then replica count will not be used | true |
|
||||
| `autoscaler.minReplicas` | Minimum no of replicas | 1 |
|
||||
| `autoscaler.maxReplicas` | Maximum no of replicas | 5 |
|
||||
| `autoscaler.scaleDownDelaySecondsAfterScaleOut` | [Anti-Flapping Configuration](https://github.com/actions-runner-controller/actions-runner-controller#anti-flapping-configuration) | 120 |
|
||||
| `autoscaler.metrics` | [Pull driven scaling](https://github.com/actions-runner-controller/actions-runner-controller#pull-driven-scaling) | default |
|
||||
| `autoscaler.scaleUpTriggers` | [Webhook driven scaling](https://github.com/actions-runner-controller/actions-runner-controller#webhook-driven-scaling) | |
|
||||
54
contrib/examples/actions-runner/templates/_helpers.tpl
Normal file
54
contrib/examples/actions-runner/templates/_helpers.tpl
Normal file
@@ -0,0 +1,54 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "actions-runner.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- 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.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "actions-runner.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "actions-runner.labels" -}}
|
||||
helm.sh/chart: {{ include "actions-runner.chart" . }}
|
||||
{{ include "actions-runner.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- range $k, $v := .Values.labels }}
|
||||
{{ $k }}: {{ $v }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "actions-runner.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "actions-runner.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
96
contrib/examples/actions-runner/templates/deployment.yaml
Normal file
96
contrib/examples/actions-runner/templates/deployment.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: {{ include "actions-runner.fullname" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "actions-runner.labels" . | nindent 6 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaler.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "actions-runner.selectorLabels" . | nindent 8 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
kubectl.kubernetes.io/default-logs-container: "runner"
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "actions-runner.selectorLabels" . | nindent 10 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 10 }}
|
||||
{{- with .Values.priorityClassName }}
|
||||
priorityClassName: "{{ . }}"
|
||||
{{- end }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if .Values.organization }}
|
||||
organization: {{ .Values.organization }}
|
||||
{{- end }}
|
||||
{{- if .Values.repository }}
|
||||
repository: {{ .Values.repository }}
|
||||
{{- end }}
|
||||
group: {{ .Values.group | default "Default" }}
|
||||
{{- with .Values.runnerLabels }}
|
||||
labels:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- if .Values.env }}
|
||||
env:
|
||||
{{- range $key, $val := .Values.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $val | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaler.enabled }}
|
||||
- apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: HorizontalRunnerAutoscaler
|
||||
metadata:
|
||||
name: {{ (list (include "actions-runner.fullname" .) "autoscaler" | join "-") }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "actions-runner.labels" . | nindent 6 }}
|
||||
spec:
|
||||
{{- if .Values.autoscaler.scaleDownDelaySecondsAfterScaleOut }}
|
||||
scaleDownDelaySecondsAfterScaleOut: {{ .Values.autoscaler.scaleDownDelaySecondsAfterScaleOut }}
|
||||
{{- end }}
|
||||
scaleTargetRef:
|
||||
name: {{ include "actions-runner.fullname" . }}
|
||||
minReplicas: {{ .Values.autoscaler.minReplicas }}
|
||||
maxReplicas: {{ .Values.autoscaler.maxReplicas }}
|
||||
{{- with .Values.autoscaler.scaleUpTriggers }}
|
||||
scaleUpTriggers:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.autoscaler.metrics }}
|
||||
metrics:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
63
contrib/examples/actions-runner/values.yaml
Normal file
63
contrib/examples/actions-runner/values.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
image:
|
||||
repository: summerwind/actions-runner
|
||||
tag: v2.290.1-ubuntu-20.04
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# Create runner for an organization or a repository
|
||||
# Set only one of the two either organization or repository
|
||||
# By default, it creates runner under github organization test
|
||||
organization: test
|
||||
# repository: mumoshu/actions-runner-controller-ci
|
||||
|
||||
# Labels you want to add in your runner
|
||||
runnerLabels:
|
||||
- test
|
||||
|
||||
# If you enable Autoscaler, then it will not be used
|
||||
replicaCount: 1
|
||||
|
||||
# The Runner Group that the runner(s) should be associated with.
|
||||
# See https://docs.github.com/en/github-ae@latest/actions/hosting-your-own-runners/managing-access-to-self-hosted-runners-using-groups.
|
||||
group: Default
|
||||
|
||||
autoscaler:
|
||||
enabled: true
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
scaleDownDelaySecondsAfterScaleOut: 120
|
||||
# metrics (pull method) / scaleUpTriggers (push method)
|
||||
# https://github.com/actions-runner-controller/actions-runner-controller#pull-driven-scaling
|
||||
# https://github.com/actions-runner-controller/actions-runner-controller#webhook-driven-scaling
|
||||
metrics:
|
||||
- type: PercentageRunnersBusy
|
||||
scaleUpThreshold: '0.75'
|
||||
scaleDownThreshold: '0.25'
|
||||
scaleUpFactor: '2'
|
||||
scaleDownFactor: '0.5'
|
||||
# scaleUpTriggers:
|
||||
# - githubEvent: {}
|
||||
# duration: "5m"
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podLabels: {}
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
|
||||
# Leverage a PriorityClass to ensure your pods survive resource shortages
|
||||
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
|
||||
# PriorityClass: system-cluster-critical
|
||||
priorityClassName: ""
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
env:
|
||||
{}
|
||||
60
contrib/examples/terraform/actions-runner-controller.tf
Normal file
60
contrib/examples/terraform/actions-runner-controller.tf
Normal file
@@ -0,0 +1,60 @@
|
||||
### Deploying with exposed github token
|
||||
|
||||
resource "kubernetes_namespace" "arc" {
|
||||
metadata {
|
||||
name = "actions-runner-system"
|
||||
}
|
||||
}
|
||||
|
||||
resource "helm_release" "actions-runner-controller" {
|
||||
count = var.actions_runner_controller
|
||||
name = "actions-runner-controller"
|
||||
namespace = kubernetes_namespace.arc.metadata[0].name
|
||||
create_namespace = true
|
||||
chart = "actions-runner-controller"
|
||||
repository = "https://actions-runner-controller.github.io/actions-runner-controller"
|
||||
version = "v0.19.1"
|
||||
values = [<<EOF
|
||||
authSecret:
|
||||
github_token: hdjasyd7das7d7asd78as87dasdas
|
||||
create: true
|
||||
EOF
|
||||
]
|
||||
depends_on = [resource.helm_release.cm]
|
||||
}
|
||||
|
||||
#============================================================================================================================================
|
||||
### Deploying with secret manager like AWS's
|
||||
# make sure the name of the secret is the same as secret_id
|
||||
|
||||
data "aws_secretsmanager_secret_version" "creds" {
|
||||
secret_id = "github/access_token"
|
||||
}
|
||||
locals {
|
||||
github_creds = jsondecode(
|
||||
data.aws_secretsmanager_secret_version.creds.secret_string
|
||||
)
|
||||
}
|
||||
|
||||
resource "kubernetes_namespace" "arc" {
|
||||
metadata {
|
||||
name = "actions-runner-system"
|
||||
}
|
||||
}
|
||||
|
||||
resource "helm_release" "actions-runner-controller" {
|
||||
count = var.actions_runner_controller
|
||||
name = "actions-runner-controller"
|
||||
namespace = kubernetes_namespace.arc.metadata[0].name
|
||||
create_namespace = true
|
||||
chart = "actions-runner-controller"
|
||||
repository = "https://actions-runner-controller.github.io/actions-runner-controller"
|
||||
version = "v0.19.1"
|
||||
values = [<<EOF
|
||||
authSecret:
|
||||
github_token: ${local.github_creds.github_token}
|
||||
create: true
|
||||
EOF
|
||||
]
|
||||
depends_on = [resource.helm_release.cm]
|
||||
}
|
||||
27
contrib/examples/terraform/cert-manager.tf
Normal file
27
contrib/examples/terraform/cert-manager.tf
Normal file
@@ -0,0 +1,27 @@
|
||||
# cert-manager must be deployed or included via the deployment process
|
||||
|
||||
resource "kubernetes_namespace" "cm" {
|
||||
metadata {
|
||||
name = "cert-manager"
|
||||
}
|
||||
}
|
||||
|
||||
resource "helm_release" "cm" {
|
||||
count = var.actions_runner_controller
|
||||
name = "cm"
|
||||
namespace = kubernetes_namespace.cm.metadata[0].name
|
||||
create_namespace = true
|
||||
chart = "cert-manager"
|
||||
repository = "https://charts.jetstack.io"
|
||||
version = "v1.8.0"
|
||||
values = [<<EOF
|
||||
global:
|
||||
podSecurityPolicy:
|
||||
enabled: true
|
||||
useAppArmor: true
|
||||
prometheus:
|
||||
enabled: false
|
||||
installCRDs: true
|
||||
EOF
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/google/go-github/v39/github"
|
||||
prometheus_metrics "github.com/actions-runner-controller/actions-runner-controller/controllers/metrics"
|
||||
arcgithub "github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -19,7 +23,7 @@ const (
|
||||
defaultScaleDownFactor = 0.7
|
||||
)
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestDesiredReplicas(st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestDesiredReplicas(ghc *arcgithub.Client, st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, error) {
|
||||
if hra.Spec.MinReplicas == nil {
|
||||
return nil, fmt.Errorf("horizontalrunnerautoscaler %s/%s is missing minReplicas", hra.Namespace, hra.Name)
|
||||
} else if hra.Spec.MaxReplicas == nil {
|
||||
@@ -46,9 +50,9 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestDesiredReplicas(st scaleTa
|
||||
|
||||
switch primaryMetricType {
|
||||
case v1alpha1.AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns:
|
||||
suggested, err = r.suggestReplicasByQueuedAndInProgressWorkflowRuns(st, hra, &primaryMetric)
|
||||
suggested, err = r.suggestReplicasByQueuedAndInProgressWorkflowRuns(ghc, st, hra, &primaryMetric)
|
||||
case v1alpha1.AutoscalingMetricTypePercentageRunnersBusy:
|
||||
suggested, err = r.suggestReplicasByPercentageRunnersBusy(st, hra, primaryMetric)
|
||||
suggested, err = r.suggestReplicasByPercentageRunnersBusy(ghc, st, hra, primaryMetric)
|
||||
default:
|
||||
return nil, fmt.Errorf("validating autoscaling metrics: unsupported metric type %q", primaryMetric)
|
||||
}
|
||||
@@ -81,11 +85,10 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestDesiredReplicas(st scaleTa
|
||||
)
|
||||
}
|
||||
|
||||
return r.suggestReplicasByQueuedAndInProgressWorkflowRuns(st, hra, &fallbackMetric)
|
||||
return r.suggestReplicasByQueuedAndInProgressWorkflowRuns(ghc, st, hra, &fallbackMetric)
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgressWorkflowRuns(st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, metrics *v1alpha1.MetricSpec) (*int, error) {
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgressWorkflowRuns(ghc *arcgithub.Client, st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, metrics *v1alpha1.MetricSpec) (*int, error) {
|
||||
var repos [][]string
|
||||
repoID := st.repo
|
||||
if repoID == "" {
|
||||
@@ -124,7 +127,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
|
||||
opt := github.ListWorkflowJobsOptions{ListOptions: github.ListOptions{PerPage: 50}}
|
||||
var allJobs []*github.WorkflowJob
|
||||
for {
|
||||
jobs, resp, err := r.GitHubClient.Actions.ListWorkflowJobs(context.TODO(), user, repoName, runID, &opt)
|
||||
jobs, resp, err := ghc.Actions.ListWorkflowJobs(context.TODO(), user, repoName, runID, &opt)
|
||||
if err != nil {
|
||||
r.Log.Error(err, "Error listing workflow jobs")
|
||||
return //err
|
||||
@@ -182,7 +185,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
|
||||
|
||||
for _, repo := range repos {
|
||||
user, repoName := repo[0], repo[1]
|
||||
workflowRuns, err := r.GitHubClient.ListRepositoryWorkflowRuns(context.TODO(), user, repoName)
|
||||
workflowRuns, err := ghc.ListRepositoryWorkflowRuns(context.TODO(), user, repoName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -209,6 +212,20 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
|
||||
|
||||
necessaryReplicas := queued + inProgress
|
||||
|
||||
prometheus_metrics.SetHorizontalRunnerAutoscalerQueuedAndInProgressWorkflowRuns(
|
||||
hra.ObjectMeta,
|
||||
st.enterprise,
|
||||
st.org,
|
||||
st.repo,
|
||||
st.kind,
|
||||
st.st,
|
||||
necessaryReplicas,
|
||||
completed,
|
||||
inProgress,
|
||||
queued,
|
||||
unknown,
|
||||
)
|
||||
|
||||
r.Log.V(1).Info(
|
||||
fmt.Sprintf("Suggested desired replicas of %d by TotalNumberOfQueuedAndInProgressWorkflowRuns", necessaryReplicas),
|
||||
"workflow_runs_completed", completed,
|
||||
@@ -224,7 +241,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByQueuedAndInProgr
|
||||
return &necessaryReplicas, nil
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunnersBusy(st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, metrics v1alpha1.MetricSpec) (*int, error) {
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunnersBusy(ghc *arcgithub.Client, st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, metrics v1alpha1.MetricSpec) (*int, error) {
|
||||
ctx := context.Background()
|
||||
scaleUpThreshold := defaultScaleUpThreshold
|
||||
scaleDownThreshold := defaultScaleDownThreshold
|
||||
@@ -293,7 +310,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunner
|
||||
)
|
||||
|
||||
// ListRunners will return all runners managed by GitHub - not restricted to ns
|
||||
runners, err := r.GitHubClient.ListRunners(
|
||||
runners, err := ghc.ListRunners(
|
||||
ctx,
|
||||
enterprise,
|
||||
organization,
|
||||
@@ -314,22 +331,52 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunner
|
||||
numRunners int
|
||||
numRunnersRegistered int
|
||||
numRunnersBusy int
|
||||
numTerminatingBusy int
|
||||
)
|
||||
|
||||
numRunners = len(runnerMap)
|
||||
|
||||
busyTerminatingRunnerPods := map[string]struct{}{}
|
||||
|
||||
kindLabel := LabelKeyRunnerDeploymentName
|
||||
if hra.Spec.ScaleTargetRef.Kind == "RunnerSet" {
|
||||
kindLabel = LabelKeyRunnerSetName
|
||||
}
|
||||
|
||||
var runnerPodList corev1.PodList
|
||||
if err := r.Client.List(ctx, &runnerPodList, client.InNamespace(hra.Namespace), client.MatchingLabels(map[string]string{
|
||||
kindLabel: hra.Spec.ScaleTargetRef.Name,
|
||||
})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, p := range runnerPodList.Items {
|
||||
if p.Annotations[AnnotationKeyUnregistrationFailureMessage] != "" {
|
||||
busyTerminatingRunnerPods[p.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, runner := range runners {
|
||||
if _, ok := runnerMap[*runner.Name]; ok {
|
||||
numRunnersRegistered++
|
||||
|
||||
if runner.GetBusy() {
|
||||
numRunnersBusy++
|
||||
} else if _, ok := busyTerminatingRunnerPods[*runner.Name]; ok {
|
||||
numTerminatingBusy++
|
||||
}
|
||||
|
||||
delete(busyTerminatingRunnerPods, *runner.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Remaining busyTerminatingRunnerPods are runners that were not on the ListRunners API response yet
|
||||
for range busyTerminatingRunnerPods {
|
||||
numTerminatingBusy++
|
||||
}
|
||||
|
||||
var desiredReplicas int
|
||||
fractionBusy := float64(numRunnersBusy) / float64(desiredReplicasBefore)
|
||||
fractionBusy := float64(numRunnersBusy+numTerminatingBusy) / float64(desiredReplicasBefore)
|
||||
if fractionBusy >= scaleUpThreshold {
|
||||
if scaleUpAdjustment > 0 {
|
||||
desiredReplicas = desiredReplicasBefore + scaleUpAdjustment
|
||||
@@ -350,6 +397,19 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunner
|
||||
//
|
||||
// - num_runners can be as twice as large as replicas_desired_before while
|
||||
// the runnerdeployment controller is replacing RunnerReplicaSet for runner update.
|
||||
prometheus_metrics.SetHorizontalRunnerAutoscalerPercentageRunnersBusy(
|
||||
hra.ObjectMeta,
|
||||
st.enterprise,
|
||||
st.org,
|
||||
st.repo,
|
||||
st.kind,
|
||||
st.st,
|
||||
desiredReplicas,
|
||||
numRunners,
|
||||
numRunnersRegistered,
|
||||
numRunnersBusy,
|
||||
numTerminatingBusy,
|
||||
)
|
||||
|
||||
r.Log.V(1).Info(
|
||||
fmt.Sprintf("Suggested desired replicas of %d by PercentageRunnersBusy", desiredReplicas),
|
||||
@@ -358,6 +418,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) suggestReplicasByPercentageRunner
|
||||
"num_runners", numRunners,
|
||||
"num_runners_registered", numRunnersRegistered,
|
||||
"num_runners_busy", numRunnersBusy,
|
||||
"num_terminating_busy", numTerminatingBusy,
|
||||
"namespace", hra.Namespace,
|
||||
"kind", st.kind,
|
||||
"name", st.st,
|
||||
|
||||
@@ -330,7 +330,6 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
|
||||
|
||||
h := &HorizontalRunnerAutoscalerReconciler{
|
||||
Log: log,
|
||||
GitHubClient: client,
|
||||
Scheme: scheme,
|
||||
DefaultScaleDownDelay: DefaultScaleDownDelay,
|
||||
}
|
||||
@@ -379,7 +378,7 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
|
||||
|
||||
st := h.scaleTargetFromRD(context.Background(), rd)
|
||||
|
||||
got, err := h.computeReplicasWithCache(log, metav1Now.Time, st, hra, minReplicas)
|
||||
got, err := h.computeReplicasWithCache(client, log, metav1Now.Time, st, hra, minReplicas)
|
||||
if err != nil {
|
||||
if tc.err == "" {
|
||||
t.Fatalf("unexpected error: expected none, got %v", err)
|
||||
@@ -720,7 +719,6 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
|
||||
h := &HorizontalRunnerAutoscalerReconciler{
|
||||
Log: log,
|
||||
Scheme: scheme,
|
||||
GitHubClient: client,
|
||||
DefaultScaleDownDelay: DefaultScaleDownDelay,
|
||||
}
|
||||
|
||||
@@ -781,7 +779,7 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
|
||||
|
||||
st := h.scaleTargetFromRD(context.Background(), rd)
|
||||
|
||||
got, err := h.computeReplicasWithCache(log, metav1Now.Time, st, hra, minReplicas)
|
||||
got, err := h.computeReplicasWithCache(client, log, metav1Now.Time, st, hra, minReplicas)
|
||||
if err != nil {
|
||||
if tc.err == "" {
|
||||
t.Fatalf("unexpected error: expected none, got %v", err)
|
||||
|
||||
@@ -4,17 +4,22 @@ import "time"
|
||||
|
||||
const (
|
||||
LabelKeyRunnerSetName = "runnerset-name"
|
||||
LabelKeyRunner = "actions-runner"
|
||||
)
|
||||
|
||||
const (
|
||||
// This names requires at least one slash to work.
|
||||
// See https://github.com/google/knative-gcp/issues/378
|
||||
runnerPodFinalizerName = "actions.summerwind.dev/runner-pod"
|
||||
runnerPodFinalizerName = "actions.summerwind.dev/runner-pod"
|
||||
runnerLinkedResourcesFinalizerName = "actions.summerwind.dev/linked-resources"
|
||||
|
||||
annotationKeyPrefix = "actions-runner/"
|
||||
|
||||
AnnotationKeyLastRegistrationCheckTime = "actions-runner-controller/last-registration-check-time"
|
||||
|
||||
// AnnotationKeyUnregistrationFailureMessage is the annotation that is added onto the pod once it failed to be unregistered from GitHub due to e.g. 422 error
|
||||
AnnotationKeyUnregistrationFailureMessage = annotationKeyPrefix + "unregistration-failure-message"
|
||||
|
||||
// AnnotationKeyUnregistrationCompleteTimestamp is the annotation that is added onto the pod once the previously started unregistration process has been completed.
|
||||
AnnotationKeyUnregistrationCompleteTimestamp = annotationKeyPrefix + "unregistration-complete-timestamp"
|
||||
|
||||
@@ -61,4 +66,7 @@ const (
|
||||
|
||||
EnvVarRunnerName = "RUNNER_NAME"
|
||||
EnvVarRunnerToken = "RUNNER_TOKEN"
|
||||
|
||||
// defaultHookPath is path to the hook script used when the "containerMode: kubernetes" is specified
|
||||
defaultRunnerHookPath = "/runner/k8s/index.js"
|
||||
)
|
||||
|
||||
206
controllers/horizontal_runner_autoscaler_batch_scale.go
Normal file
206
controllers/horizontal_runner_autoscaler_batch_scale.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type batchScaler struct {
|
||||
Ctx context.Context
|
||||
Client client.Client
|
||||
Log logr.Logger
|
||||
interval time.Duration
|
||||
|
||||
queue chan *ScaleTarget
|
||||
workerStart sync.Once
|
||||
}
|
||||
|
||||
func newBatchScaler(ctx context.Context, client client.Client, log logr.Logger) *batchScaler {
|
||||
return &batchScaler{
|
||||
Ctx: ctx,
|
||||
Client: client,
|
||||
Log: log,
|
||||
interval: 3 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
type batchScaleOperation struct {
|
||||
namespacedName types.NamespacedName
|
||||
scaleOps []scaleOperation
|
||||
}
|
||||
|
||||
type scaleOperation struct {
|
||||
trigger v1alpha1.ScaleUpTrigger
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
// Add the scale target to the unbounded queue, blocking until the target is successfully added to the queue.
|
||||
// All the targets in the queue are dequeued every 3 seconds, grouped by the HRA, and applied.
|
||||
// In a happy path, batchScaler update each HRA only once, even though the HRA had two or more associated webhook events in the 3 seconds interval,
|
||||
// which results in less K8s API calls and less HRA update conflicts in case your ARC installation receives a lot of webhook events
|
||||
func (s *batchScaler) Add(st *ScaleTarget) {
|
||||
if st == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.workerStart.Do(func() {
|
||||
var expBackoff = []time.Duration{time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second}
|
||||
|
||||
s.queue = make(chan *ScaleTarget)
|
||||
|
||||
log := s.Log
|
||||
|
||||
go func() {
|
||||
log.Info("Starting batch worker")
|
||||
defer log.Info("Stopped batch worker")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.Ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
log.V(2).Info("Batch worker is dequeueing operations")
|
||||
|
||||
batches := map[types.NamespacedName]batchScaleOperation{}
|
||||
after := time.After(s.interval)
|
||||
var ops uint
|
||||
|
||||
batch:
|
||||
for {
|
||||
select {
|
||||
case <-after:
|
||||
break batch
|
||||
case st := <-s.queue:
|
||||
nsName := types.NamespacedName{
|
||||
Namespace: st.HorizontalRunnerAutoscaler.Namespace,
|
||||
Name: st.HorizontalRunnerAutoscaler.Name,
|
||||
}
|
||||
b, ok := batches[nsName]
|
||||
if !ok {
|
||||
b = batchScaleOperation{
|
||||
namespacedName: nsName,
|
||||
}
|
||||
}
|
||||
b.scaleOps = append(b.scaleOps, scaleOperation{
|
||||
log: *st.log,
|
||||
trigger: st.ScaleUpTrigger,
|
||||
})
|
||||
batches[nsName] = b
|
||||
ops++
|
||||
}
|
||||
}
|
||||
|
||||
log.V(2).Info("Batch worker dequeued operations", "ops", ops, "batches", len(batches))
|
||||
|
||||
retry:
|
||||
for i := 0; ; i++ {
|
||||
failed := map[types.NamespacedName]batchScaleOperation{}
|
||||
|
||||
for nsName, b := range batches {
|
||||
b := b
|
||||
if err := s.batchScale(context.Background(), b); err != nil {
|
||||
log.V(2).Info("Failed to scale due to error", "error", err)
|
||||
failed[nsName] = b
|
||||
} else {
|
||||
log.V(2).Info("Successfully ran batch scale", "hra", b.namespacedName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(failed) == 0 {
|
||||
break retry
|
||||
}
|
||||
|
||||
batches = failed
|
||||
|
||||
delay := 16 * time.Second
|
||||
if i < len(expBackoff) {
|
||||
delay = expBackoff[i]
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
s.queue <- st
|
||||
}
|
||||
|
||||
func (s *batchScaler) batchScale(ctx context.Context, batch batchScaleOperation) error {
|
||||
var hra v1alpha1.HorizontalRunnerAutoscaler
|
||||
|
||||
if err := s.Client.Get(ctx, batch.namespacedName, &hra); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copy := hra.DeepCopy()
|
||||
|
||||
copy.Spec.CapacityReservations = getValidCapacityReservations(copy)
|
||||
|
||||
var added, completed int
|
||||
|
||||
for _, scale := range batch.scaleOps {
|
||||
amount := 1
|
||||
|
||||
if scale.trigger.Amount != 0 {
|
||||
amount = scale.trigger.Amount
|
||||
}
|
||||
|
||||
scale.log.V(2).Info("Adding capacity reservation", "amount", amount)
|
||||
|
||||
if amount > 0 {
|
||||
now := time.Now()
|
||||
copy.Spec.CapacityReservations = append(copy.Spec.CapacityReservations, v1alpha1.CapacityReservation{
|
||||
EffectiveTime: metav1.Time{Time: now},
|
||||
ExpirationTime: metav1.Time{Time: now.Add(scale.trigger.Duration.Duration)},
|
||||
Replicas: amount,
|
||||
})
|
||||
|
||||
added += amount
|
||||
} else if amount < 0 {
|
||||
var reservations []v1alpha1.CapacityReservation
|
||||
|
||||
var found bool
|
||||
|
||||
for _, r := range copy.Spec.CapacityReservations {
|
||||
if !found && r.Replicas+amount == 0 {
|
||||
found = true
|
||||
} else {
|
||||
reservations = append(reservations, r)
|
||||
}
|
||||
}
|
||||
|
||||
copy.Spec.CapacityReservations = reservations
|
||||
|
||||
completed += amount
|
||||
}
|
||||
}
|
||||
|
||||
before := len(hra.Spec.CapacityReservations)
|
||||
expired := before - len(copy.Spec.CapacityReservations)
|
||||
after := len(copy.Spec.CapacityReservations)
|
||||
|
||||
s.Log.V(1).Info(
|
||||
fmt.Sprintf("Updating hra %s for capacityReservations update", hra.Name),
|
||||
"before", before,
|
||||
"expired", expired,
|
||||
"added", added,
|
||||
"completed", completed,
|
||||
"after", after,
|
||||
)
|
||||
|
||||
if err := s.Client.Update(ctx, copy); err != nil {
|
||||
return fmt.Errorf("updating horizontalrunnerautoscaler to add capacity reservation: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -20,17 +20,17 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
gogithub "github.com/google/go-github/v39/github"
|
||||
gogithub "github.com/google/go-github/v47/github"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -46,6 +46,8 @@ const (
|
||||
|
||||
keyPrefixEnterprise = "enterprises/"
|
||||
keyRunnerGroup = "/group/"
|
||||
|
||||
DefaultQueueLimit = 100
|
||||
)
|
||||
|
||||
// HorizontalRunnerAutoscalerGitHubWebhook autoscales a HorizontalRunnerAutoscaler and the RunnerDeployment on each
|
||||
@@ -68,6 +70,13 @@ type HorizontalRunnerAutoscalerGitHubWebhook struct {
|
||||
// Set to empty for letting it watch for all namespaces.
|
||||
Namespace string
|
||||
Name string
|
||||
|
||||
// QueueLimit is the maximum length of the bounded queue of scale targets and their associated operations
|
||||
// A scale target is enqueued on each retrieval of each eligible webhook event, so that it is processed asynchronously.
|
||||
QueueLimit int
|
||||
|
||||
worker *worker
|
||||
workerInit sync.Once
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Reconcile(_ context.Context, request reconcile.Request) (reconcile.Result, error) {
|
||||
@@ -122,7 +131,7 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.Respons
|
||||
return
|
||||
}
|
||||
} else {
|
||||
payload, err = ioutil.ReadAll(r.Body)
|
||||
payload, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
autoscaler.Log.Error(err, "error reading request body")
|
||||
|
||||
@@ -312,9 +321,19 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) Handle(w http.Respons
|
||||
return
|
||||
}
|
||||
|
||||
if err := autoscaler.tryScale(context.TODO(), target); err != nil {
|
||||
log.Error(err, "could not scale up")
|
||||
autoscaler.workerInit.Do(func() {
|
||||
batchScaler := newBatchScaler(context.Background(), autoscaler.Client, autoscaler.Log)
|
||||
|
||||
queueLimit := autoscaler.QueueLimit
|
||||
if queueLimit == 0 {
|
||||
queueLimit = DefaultQueueLimit
|
||||
}
|
||||
autoscaler.worker = newWorker(context.Background(), queueLimit, batchScaler.Add)
|
||||
})
|
||||
|
||||
target.log = &log
|
||||
if ok := autoscaler.worker.Add(target); !ok {
|
||||
log.Error(err, "Could not scale up due to queue full")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -383,6 +402,8 @@ func matchTriggerConditionAgainstEvent(types []string, eventAction *string) bool
|
||||
type ScaleTarget struct {
|
||||
v1alpha1.HorizontalRunnerAutoscaler
|
||||
v1alpha1.ScaleUpTrigger
|
||||
|
||||
log *logr.Logger
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) searchScaleTargets(hras []v1alpha1.HorizontalRunnerAutoscaler, f func(v1alpha1.ScaleUpTrigger) bool) []ScaleTarget {
|
||||
@@ -501,6 +522,7 @@ func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) getScaleUpTargetWithF
|
||||
if autoscaler.GitHubClient != nil {
|
||||
simu := &simulator.Simulator{
|
||||
Client: autoscaler.GitHubClient,
|
||||
Log: log,
|
||||
}
|
||||
// Get available organization runner groups and enterprise runner groups for a repository
|
||||
// These are the sum of runner groups with repository access = All repositories and runner groups
|
||||
@@ -770,63 +792,6 @@ HRA:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) tryScale(ctx context.Context, target *ScaleTarget) error {
|
||||
if target == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
copy := target.HorizontalRunnerAutoscaler.DeepCopy()
|
||||
|
||||
amount := 1
|
||||
|
||||
if target.ScaleUpTrigger.Amount != 0 {
|
||||
amount = target.ScaleUpTrigger.Amount
|
||||
}
|
||||
|
||||
capacityReservations := getValidCapacityReservations(copy)
|
||||
|
||||
if amount > 0 {
|
||||
now := time.Now()
|
||||
copy.Spec.CapacityReservations = append(capacityReservations, v1alpha1.CapacityReservation{
|
||||
EffectiveTime: metav1.Time{Time: now},
|
||||
ExpirationTime: metav1.Time{Time: now.Add(target.ScaleUpTrigger.Duration.Duration)},
|
||||
Replicas: amount,
|
||||
})
|
||||
} else if amount < 0 {
|
||||
var reservations []v1alpha1.CapacityReservation
|
||||
|
||||
var found bool
|
||||
|
||||
for _, r := range capacityReservations {
|
||||
if !found && r.Replicas+amount == 0 {
|
||||
found = true
|
||||
} else {
|
||||
reservations = append(reservations, r)
|
||||
}
|
||||
}
|
||||
|
||||
copy.Spec.CapacityReservations = reservations
|
||||
}
|
||||
|
||||
before := len(target.HorizontalRunnerAutoscaler.Spec.CapacityReservations)
|
||||
expired := before - len(capacityReservations)
|
||||
after := len(copy.Spec.CapacityReservations)
|
||||
|
||||
autoscaler.Log.V(1).Info(
|
||||
fmt.Sprintf("Patching hra %s for capacityReservations update", target.HorizontalRunnerAutoscaler.Name),
|
||||
"before", before,
|
||||
"expired", expired,
|
||||
"amount", amount,
|
||||
"after", after,
|
||||
)
|
||||
|
||||
if err := autoscaler.Client.Patch(ctx, copy, client.MergeFrom(&target.HorizontalRunnerAutoscaler)); err != nil {
|
||||
return fmt.Errorf("patching horizontalrunnerautoscaler to add capacity reservation: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getValidCapacityReservations(autoscaler *v1alpha1.HorizontalRunnerAutoscaler) []v1alpha1.CapacityReservation {
|
||||
var capacityReservations []v1alpha1.CapacityReservation
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package controllers
|
||||
import (
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/pkg/actionsglob"
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
)
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchCheckRunEvent(event *github.CheckRunEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
||||
|
||||
@@ -2,7 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
)
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPullRequestEvent(event *github.PullRequestEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
||||
|
||||
@@ -2,7 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
)
|
||||
|
||||
func (autoscaler *HorizontalRunnerAutoscalerGitHubWebhook) MatchPushEvent(event *github.PushEvent) func(scaleUpTrigger v1alpha1.ScaleUpTrigger) bool {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -15,7 +14,7 @@ import (
|
||||
|
||||
actionsv1alpha1 "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
@@ -504,7 +503,7 @@ func testServerWithInitObjs(t *testing.T, eventType string, event interface{}, w
|
||||
|
||||
hraWebhook := &HorizontalRunnerAutoscalerGitHubWebhook{}
|
||||
|
||||
client := fake.NewFakeClientWithScheme(sc, initObjs...)
|
||||
client := fake.NewClientBuilder().WithScheme(sc).WithRuntimeObjects(initObjs...).Build()
|
||||
|
||||
logs := installTestLogger(hraWebhook)
|
||||
|
||||
@@ -537,7 +536,7 @@ func testServerWithInitObjs(t *testing.T, eventType string, event interface{}, w
|
||||
t.Error("status:", resp.StatusCode)
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -575,7 +574,7 @@ func sendWebhook(server *httptest.Server, eventType string, event interface{}) (
|
||||
"X-GitHub-Event": {eventType},
|
||||
"Content-Type": {"application/json"},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(reqBody)),
|
||||
Body: io.NopCloser(bytes.NewBuffer(reqBody)),
|
||||
}
|
||||
|
||||
return http.DefaultClient.Do(req)
|
||||
@@ -607,7 +606,7 @@ func (l *testLogSink) Info(_ int, msg string, kvs ...interface{}) {
|
||||
fmt.Fprintf(l.writer, "\n")
|
||||
}
|
||||
|
||||
func (_ *testLogSink) Enabled(level int) bool {
|
||||
func (*testLogSink) Enabled(level int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
55
controllers/horizontal_runner_autoscaler_webhook_worker.go
Normal file
55
controllers/horizontal_runner_autoscaler_webhook_worker.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// worker is a worker that has a non-blocking bounded queue of scale targets, dequeues scale target and executes the scale operation one by one.
|
||||
type worker struct {
|
||||
scaleTargetQueue chan *ScaleTarget
|
||||
work func(*ScaleTarget)
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newWorker(ctx context.Context, queueLimit int, work func(*ScaleTarget)) *worker {
|
||||
w := &worker{
|
||||
scaleTargetQueue: make(chan *ScaleTarget, queueLimit),
|
||||
work: work,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(w.done)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case t := <-w.scaleTargetQueue:
|
||||
work(t)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// Add the scale target to the bounded queue, returning the result as a bool value. It returns true on successful enqueue, and returns false otherwise.
|
||||
// When returned false, the queue is already full so the enqueue operation must be retried later.
|
||||
// If the enqueue was triggered by an external source and there's no intermediate queue that we can use,
|
||||
// you must instruct the source to resend the original request later.
|
||||
// In case you're building a webhook server around this worker, this means that you must return a http error to the webhook server,
|
||||
// so that (hopefully) the sender can resend the webhook event later, or at least the human operator can notice or be notified about the
|
||||
// webhook develiery failure so that a manual retry can be done later.
|
||||
func (w *worker) Add(st *ScaleTarget) bool {
|
||||
select {
|
||||
case w.scaleTargetQueue <- st:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worker) Done() chan struct{} {
|
||||
return w.done
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorker_Add(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
w := newWorker(ctx, 2, func(st *ScaleTarget) {})
|
||||
require.True(t, w.Add(&ScaleTarget{}))
|
||||
require.True(t, w.Add(&ScaleTarget{}))
|
||||
require.False(t, w.Add(&ScaleTarget{}))
|
||||
}
|
||||
|
||||
func TestWorker_Work(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var count int
|
||||
|
||||
w := newWorker(ctx, 1, func(st *ScaleTarget) {
|
||||
count++
|
||||
cancel()
|
||||
})
|
||||
require.True(t, w.Add(&ScaleTarget{}))
|
||||
require.False(t, w.Add(&ScaleTarget{}))
|
||||
|
||||
<-w.Done()
|
||||
|
||||
require.Equal(t, count, 1)
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/go-logr/logr"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -38,6 +37,7 @@ import (
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/controllers/metrics"
|
||||
arcgithub "github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,11 +47,10 @@ const (
|
||||
// HorizontalRunnerAutoscalerReconciler reconciles a HorizontalRunnerAutoscaler object
|
||||
type HorizontalRunnerAutoscalerReconciler struct {
|
||||
client.Client
|
||||
GitHubClient *github.Client
|
||||
GitHubClient *MultiGitHubClient
|
||||
Log logr.Logger
|
||||
Recorder record.EventRecorder
|
||||
Scheme *runtime.Scheme
|
||||
CacheDuration time.Duration
|
||||
DefaultScaleDownDelay time.Duration
|
||||
Name string
|
||||
}
|
||||
@@ -73,6 +72,8 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(ctx context.Context, re
|
||||
}
|
||||
|
||||
if !hra.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
r.GitHubClient.DeinitForHRA(&hra)
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -310,7 +311,12 @@ func (r *HorizontalRunnerAutoscalerReconciler) reconcile(ctx context.Context, re
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
newDesiredReplicas, err := r.computeReplicasWithCache(log, now, st, hra, minReplicas)
|
||||
ghc, err := r.GitHubClient.InitForHRA(context.Background(), &hra)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
newDesiredReplicas, err := r.computeReplicasWithCache(ghc, log, now, st, hra, minReplicas)
|
||||
if err != nil {
|
||||
r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
|
||||
|
||||
@@ -461,10 +467,10 @@ func (r *HorizontalRunnerAutoscalerReconciler) getMinReplicas(log logr.Logger, n
|
||||
return minReplicas, active, upcoming, nil
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(log logr.Logger, now time.Time, st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, minReplicas int) (int, error) {
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(ghc *arcgithub.Client, log logr.Logger, now time.Time, st scaleTarget, hra v1alpha1.HorizontalRunnerAutoscaler, minReplicas int) (int, error) {
|
||||
var suggestedReplicas int
|
||||
|
||||
v, err := r.suggestDesiredReplicas(st, hra)
|
||||
v, err := r.suggestDesiredReplicas(ghc, st, hra)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
github2 "github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/google/go-github/v39/github"
|
||||
"github.com/google/go-github/v47/github"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github/fake"
|
||||
|
||||
@@ -99,12 +99,14 @@ func SetupIntegrationTest(ctx2 context.Context) *testEnvironment {
|
||||
return fmt.Sprintf("%s%s", ns.Name, name)
|
||||
}
|
||||
|
||||
multiClient := NewMultiGitHubClient(mgr.GetClient(), env.ghClient)
|
||||
|
||||
runnerController := &RunnerReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
Log: logf.Log,
|
||||
Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"),
|
||||
GitHubClient: env.ghClient,
|
||||
GitHubClient: multiClient,
|
||||
RunnerImage: "example/runner:test",
|
||||
DockerImage: "example/docker:test",
|
||||
Name: controllerName("runner"),
|
||||
@@ -116,12 +118,11 @@ func SetupIntegrationTest(ctx2 context.Context) *testEnvironment {
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to setup runner controller")
|
||||
|
||||
replicasetController := &RunnerReplicaSetReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
Log: logf.Log,
|
||||
Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"),
|
||||
GitHubClient: env.ghClient,
|
||||
Name: controllerName("runnerreplicaset"),
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
Log: logf.Log,
|
||||
Recorder: mgr.GetEventRecorderFor("runnerreplicaset-controller"),
|
||||
Name: controllerName("runnerreplicaset"),
|
||||
}
|
||||
err = replicasetController.SetupWithManager(mgr)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to setup runnerreplicaset controller")
|
||||
@@ -137,13 +138,12 @@ func SetupIntegrationTest(ctx2 context.Context) *testEnvironment {
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to setup runnerdeployment controller")
|
||||
|
||||
autoscalerController := &HorizontalRunnerAutoscalerReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
Log: logf.Log,
|
||||
GitHubClient: env.ghClient,
|
||||
Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"),
|
||||
CacheDuration: 1 * time.Second,
|
||||
Name: controllerName("horizontalrunnerautoscaler"),
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: scheme.Scheme,
|
||||
Log: logf.Log,
|
||||
GitHubClient: multiClient,
|
||||
Recorder: mgr.GetEventRecorderFor("horizontalrunnerautoscaler-controller"),
|
||||
Name: controllerName("horizontalrunnerautoscaler"),
|
||||
}
|
||||
err = autoscalerController.SetupWithManager(mgr)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to setup autoscaler controller")
|
||||
@@ -1367,7 +1367,7 @@ func (env *testEnvironment) ExpectRegisteredNumberCountEventuallyEquals(want int
|
||||
|
||||
return len(rs)
|
||||
},
|
||||
time.Second*5, time.Millisecond*500).Should(Equal(want), optionalDescriptions...)
|
||||
time.Second*10, time.Millisecond*500).Should(Equal(want), optionalDescriptions...)
|
||||
}
|
||||
|
||||
func (env *testEnvironment) SendOrgPullRequestEvent(org, repo, branch, action string) {
|
||||
|
||||
@@ -7,8 +7,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
hraName = "horizontalrunnerautoscaler"
|
||||
hraNamespace = "namespace"
|
||||
hraName = "horizontalrunnerautoscaler"
|
||||
hraNamespace = "namespace"
|
||||
stEnterprise = "enterprise"
|
||||
stOrganization = "organization"
|
||||
stRepository = "repository"
|
||||
stKind = "kind"
|
||||
stName = "name"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -16,6 +21,16 @@ var (
|
||||
horizontalRunnerAutoscalerMinReplicas,
|
||||
horizontalRunnerAutoscalerMaxReplicas,
|
||||
horizontalRunnerAutoscalerDesiredReplicas,
|
||||
horizontalRunnerAutoscalerReplicasDesired,
|
||||
horizontalRunnerAutoscalerRunners,
|
||||
horizontalRunnerAutoscalerRunnersRegistered,
|
||||
horizontalRunnerAutoscalerRunnersBusy,
|
||||
horizontalRunnerAutoscalerTerminatingBusy,
|
||||
horizontalRunnerAutoscalerNecessaryReplicas,
|
||||
horizontalRunnerAutoscalerWorkflowRunsCompleted,
|
||||
horizontalRunnerAutoscalerWorkflowRunsInProgress,
|
||||
horizontalRunnerAutoscalerWorkflowRunsQueued,
|
||||
horizontalRunnerAutoscalerWorkflowRunsUnknown,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -41,6 +56,78 @@ var (
|
||||
},
|
||||
[]string{hraName, hraNamespace},
|
||||
)
|
||||
// PercentageRunnersBusy
|
||||
horizontalRunnerAutoscalerReplicasDesired = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_replicas_desired",
|
||||
Help: "replicas_desired of PercentageRunnersBusy",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerRunners = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_runners",
|
||||
Help: "num_runners of PercentageRunnersBusy",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerRunnersRegistered = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_runners_registered",
|
||||
Help: "num_runners_registered of PercentageRunnersBusy",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerRunnersBusy = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_runners_busy",
|
||||
Help: "num_runners_busy of PercentageRunnersBusy",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerTerminatingBusy = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_terminating_busy",
|
||||
Help: "num_terminating_busy of PercentageRunnersBusy",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
// QueuedAndInProgressWorkflowRuns
|
||||
horizontalRunnerAutoscalerNecessaryReplicas = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_necessary_replicas",
|
||||
Help: "necessary_replicas of QueuedAndInProgressWorkflowRuns",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerWorkflowRunsCompleted = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_workflow_runs_completed",
|
||||
Help: "workflow_runs_completed of QueuedAndInProgressWorkflowRuns",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerWorkflowRunsInProgress = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_workflow_runs_in_progress",
|
||||
Help: "workflow_runs_in_progress of QueuedAndInProgressWorkflowRuns",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerWorkflowRunsQueued = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_workflow_runs_queued",
|
||||
Help: "workflow_runs_queued of QueuedAndInProgressWorkflowRuns",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
horizontalRunnerAutoscalerWorkflowRunsUnknown = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "horizontalrunnerautoscaler_workflow_runs_unknown",
|
||||
Help: "workflow_runs_unknown of QueuedAndInProgressWorkflowRuns",
|
||||
},
|
||||
[]string{hraName, hraNamespace, stEnterprise, stOrganization, stRepository, stKind, stName},
|
||||
)
|
||||
)
|
||||
|
||||
func SetHorizontalRunnerAutoscalerSpec(o metav1.ObjectMeta, spec v1alpha1.HorizontalRunnerAutoscalerSpec) {
|
||||
@@ -65,3 +152,61 @@ func SetHorizontalRunnerAutoscalerStatus(o metav1.ObjectMeta, status v1alpha1.Ho
|
||||
horizontalRunnerAutoscalerDesiredReplicas.With(labels).Set(float64(*status.DesiredReplicas))
|
||||
}
|
||||
}
|
||||
|
||||
func SetHorizontalRunnerAutoscalerPercentageRunnersBusy(
|
||||
o metav1.ObjectMeta,
|
||||
enterprise string,
|
||||
organization string,
|
||||
repository string,
|
||||
kind string,
|
||||
name string,
|
||||
desiredReplicas int,
|
||||
numRunners int,
|
||||
numRunnersRegistered int,
|
||||
numRunnersBusy int,
|
||||
numTerminatingBusy int,
|
||||
) {
|
||||
labels := prometheus.Labels{
|
||||
hraName: o.Name,
|
||||
hraNamespace: o.Namespace,
|
||||
stEnterprise: enterprise,
|
||||
stOrganization: organization,
|
||||
stRepository: repository,
|
||||
stKind: kind,
|
||||
stName: name,
|
||||
}
|
||||
horizontalRunnerAutoscalerReplicasDesired.With(labels).Set(float64(desiredReplicas))
|
||||
horizontalRunnerAutoscalerRunners.With(labels).Set(float64(numRunners))
|
||||
horizontalRunnerAutoscalerRunnersRegistered.With(labels).Set(float64(numRunnersRegistered))
|
||||
horizontalRunnerAutoscalerRunnersBusy.With(labels).Set(float64(numRunnersBusy))
|
||||
horizontalRunnerAutoscalerTerminatingBusy.With(labels).Set(float64(numTerminatingBusy))
|
||||
}
|
||||
|
||||
func SetHorizontalRunnerAutoscalerQueuedAndInProgressWorkflowRuns(
|
||||
o metav1.ObjectMeta,
|
||||
enterprise string,
|
||||
organization string,
|
||||
repository string,
|
||||
kind string,
|
||||
name string,
|
||||
necessaryReplicas int,
|
||||
workflowRunsCompleted int,
|
||||
workflowRunsInProgress int,
|
||||
workflowRunsQueued int,
|
||||
workflowRunsUnknown int,
|
||||
) {
|
||||
labels := prometheus.Labels{
|
||||
hraName: o.Name,
|
||||
hraNamespace: o.Namespace,
|
||||
stEnterprise: enterprise,
|
||||
stOrganization: organization,
|
||||
stRepository: repository,
|
||||
stKind: kind,
|
||||
stName: name,
|
||||
}
|
||||
horizontalRunnerAutoscalerNecessaryReplicas.With(labels).Set(float64(necessaryReplicas))
|
||||
horizontalRunnerAutoscalerWorkflowRunsCompleted.With(labels).Set(float64(workflowRunsCompleted))
|
||||
horizontalRunnerAutoscalerWorkflowRunsInProgress.With(labels).Set(float64(workflowRunsInProgress))
|
||||
horizontalRunnerAutoscalerWorkflowRunsQueued.With(labels).Set(float64(workflowRunsQueued))
|
||||
horizontalRunnerAutoscalerWorkflowRunsUnknown.With(labels).Set(float64(workflowRunsUnknown))
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@ const (
|
||||
rsNamespace = "namespace"
|
||||
)
|
||||
|
||||
var (
|
||||
runnerSetMetrics = []prometheus.Collector{
|
||||
runnerSetReplicas,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
runnerSetReplicas = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
|
||||
334
controllers/multi_githubclient.go
Normal file
334
controllers/multi_githubclient.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
// The api creds scret annotation is added by the runner controller or the runnerset controller according to runner.spec.githubAPICredentialsFrom.secretRef.name,
|
||||
// so that the runner pod controller can share the same GitHub API credentials and the instance of the GitHub API client with the upstream controllers.
|
||||
annotationKeyGitHubAPICredsSecret = annotationKeyPrefix + "github-api-creds-secret"
|
||||
)
|
||||
|
||||
type runnerOwnerRef struct {
|
||||
// kind is either StatefulSet or Runner, and populated via the owner reference in the runner pod controller or via the reconcilation target's kind in
|
||||
// runnerset and runner controllers.
|
||||
kind string
|
||||
ns, name string
|
||||
}
|
||||
|
||||
type secretRef struct {
|
||||
ns, name string
|
||||
}
|
||||
|
||||
// savedClient is the each cache entry that contains the client for the specific set of credentials,
|
||||
// like a PAT or a pair of key and cert.
|
||||
// the `hash` is a part of the savedClient not the key because we are going to keep only the client for the latest creds
|
||||
// in case the operator updated the k8s secret containing the credentials.
|
||||
type savedClient struct {
|
||||
hash string
|
||||
|
||||
// refs is the map of all the objects that references this client, used for reference counting to gc
|
||||
// the client if unneeded.
|
||||
refs map[runnerOwnerRef]struct{}
|
||||
|
||||
*github.Client
|
||||
}
|
||||
|
||||
type resourceReader interface {
|
||||
Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error
|
||||
}
|
||||
|
||||
type MultiGitHubClient struct {
|
||||
mu sync.Mutex
|
||||
|
||||
client resourceReader
|
||||
|
||||
githubClient *github.Client
|
||||
|
||||
// The saved client is freed once all its dependents disappear, or the contents of the secret changed.
|
||||
// We track dependents via a golang map embedded within the savedClient struct. Each dependent is checked on their respective Kubernetes finalizer,
|
||||
// so that we won't miss any dependent's termination.
|
||||
// The change is the secret is determined using the hash of its contents.
|
||||
clients map[secretRef]savedClient
|
||||
}
|
||||
|
||||
func NewMultiGitHubClient(client resourceReader, githubClient *github.Client) *MultiGitHubClient {
|
||||
return &MultiGitHubClient{
|
||||
client: client,
|
||||
githubClient: githubClient,
|
||||
clients: map[secretRef]savedClient{},
|
||||
}
|
||||
}
|
||||
|
||||
// Init sets up and return the *github.Client for the object.
|
||||
// In case the object (like RunnerDeployment) does not request a custom client, it returns the default client.
|
||||
func (c *MultiGitHubClient) InitForRunnerPod(ctx context.Context, pod *corev1.Pod) (*github.Client, error) {
|
||||
// These 3 default values are used only when the user created the pod directly, not via Runner, RunnerReplicaSet, RunnerDeploment, or RunnerSet resources.
|
||||
ref := refFromRunnerPod(pod)
|
||||
secretName := pod.Annotations[annotationKeyGitHubAPICredsSecret]
|
||||
|
||||
// kind can be any of Pod, Runner, RunnerReplicaSet, RunnerDeployment, or RunnerSet depending on which custom resource the user directly created.
|
||||
return c.initClientWithSecretName(ctx, pod.Namespace, secretName, ref)
|
||||
}
|
||||
|
||||
// Init sets up and return the *github.Client for the object.
|
||||
// In case the object (like RunnerDeployment) does not request a custom client, it returns the default client.
|
||||
func (c *MultiGitHubClient) InitForRunner(ctx context.Context, r *v1alpha1.Runner) (*github.Client, error) {
|
||||
var secretName string
|
||||
if r.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = r.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
// These 3 default values are used only when the user created the runner resource directly, not via RunnerReplicaSet, RunnerDeploment, or RunnerSet resources.
|
||||
ref := refFromRunner(r)
|
||||
if ref.ns != r.Namespace {
|
||||
return nil, fmt.Errorf("referencing github api creds secret from owner in another namespace is not supported yet")
|
||||
}
|
||||
|
||||
// kind can be any of Runner, RunnerReplicaSet, or RunnerDeployment depending on which custom resource the user directly created.
|
||||
return c.initClientWithSecretName(ctx, r.Namespace, secretName, ref)
|
||||
}
|
||||
|
||||
// Init sets up and return the *github.Client for the object.
|
||||
// In case the object (like RunnerDeployment) does not request a custom client, it returns the default client.
|
||||
func (c *MultiGitHubClient) InitForRunnerSet(ctx context.Context, rs *v1alpha1.RunnerSet) (*github.Client, error) {
|
||||
ref := refFromRunnerSet(rs)
|
||||
|
||||
var secretName string
|
||||
if rs.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = rs.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
return c.initClientWithSecretName(ctx, rs.Namespace, secretName, ref)
|
||||
}
|
||||
|
||||
// Init sets up and return the *github.Client for the object.
|
||||
// In case the object (like RunnerDeployment) does not request a custom client, it returns the default client.
|
||||
func (c *MultiGitHubClient) InitForHRA(ctx context.Context, hra *v1alpha1.HorizontalRunnerAutoscaler) (*github.Client, error) {
|
||||
ref := refFromHorizontalRunnerAutoscaler(hra)
|
||||
|
||||
var secretName string
|
||||
if hra.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = hra.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
return c.initClientWithSecretName(ctx, hra.Namespace, secretName, ref)
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) DeinitForRunnerPod(p *corev1.Pod) {
|
||||
secretName := p.Annotations[annotationKeyGitHubAPICredsSecret]
|
||||
c.derefClient(p.Namespace, secretName, refFromRunnerPod(p))
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) DeinitForRunner(r *v1alpha1.Runner) {
|
||||
var secretName string
|
||||
if r.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = r.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
c.derefClient(r.Namespace, secretName, refFromRunner(r))
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) DeinitForRunnerSet(rs *v1alpha1.RunnerSet) {
|
||||
var secretName string
|
||||
if rs.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = rs.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
c.derefClient(rs.Namespace, secretName, refFromRunnerSet(rs))
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) DeinitForHRA(hra *v1alpha1.HorizontalRunnerAutoscaler) {
|
||||
var secretName string
|
||||
if hra.Spec.GitHubAPICredentialsFrom != nil {
|
||||
secretName = hra.Spec.GitHubAPICredentialsFrom.SecretRef.Name
|
||||
}
|
||||
|
||||
c.derefClient(hra.Namespace, secretName, refFromHorizontalRunnerAutoscaler(hra))
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) initClientForSecret(secret *corev1.Secret, dependent *runnerOwnerRef) (*savedClient, error) {
|
||||
secRef := secretRef{
|
||||
ns: secret.Namespace,
|
||||
name: secret.Name,
|
||||
}
|
||||
|
||||
cliRef := c.clients[secRef]
|
||||
|
||||
var ks []string
|
||||
|
||||
for k := range secret.Data {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
|
||||
sort.SliceStable(ks, func(i, j int) bool { return ks[i] < ks[j] })
|
||||
|
||||
hash := sha1.New()
|
||||
for _, k := range ks {
|
||||
hash.Write(secret.Data[k])
|
||||
}
|
||||
hashStr := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
if cliRef.hash != hashStr {
|
||||
delete(c.clients, secRef)
|
||||
|
||||
conf, err := secretDataToGitHubClientConfig(secret.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fallback to the controller-wide setting if EnterpriseURL is not set and the original client is an enterprise client.
|
||||
if conf.EnterpriseURL == "" && c.githubClient.IsEnterprise {
|
||||
conf.EnterpriseURL = c.githubClient.GithubBaseURL
|
||||
}
|
||||
|
||||
cli, err := conf.NewClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cliRef = savedClient{
|
||||
hash: hashStr,
|
||||
refs: map[runnerOwnerRef]struct{}{},
|
||||
Client: cli,
|
||||
}
|
||||
|
||||
c.clients[secRef] = cliRef
|
||||
}
|
||||
|
||||
if dependent != nil {
|
||||
c.clients[secRef].refs[*dependent] = struct{}{}
|
||||
}
|
||||
|
||||
return &cliRef, nil
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) initClientWithSecretName(ctx context.Context, ns, secretName string, runRef *runnerOwnerRef) (*github.Client, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if secretName == "" {
|
||||
return c.githubClient, nil
|
||||
}
|
||||
|
||||
secRef := secretRef{
|
||||
ns: ns,
|
||||
name: secretName,
|
||||
}
|
||||
|
||||
if _, ok := c.clients[secRef]; !ok {
|
||||
c.clients[secRef] = savedClient{}
|
||||
}
|
||||
|
||||
var sec corev1.Secret
|
||||
if err := c.client.Get(ctx, types.NamespacedName{Namespace: ns, Name: secretName}, &sec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
savedClient, err := c.initClientForSecret(&sec, runRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return savedClient.Client, nil
|
||||
}
|
||||
|
||||
func (c *MultiGitHubClient) derefClient(ns, secretName string, dependent *runnerOwnerRef) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
secRef := secretRef{
|
||||
ns: ns,
|
||||
name: secretName,
|
||||
}
|
||||
|
||||
if dependent != nil {
|
||||
delete(c.clients[secRef].refs, *dependent)
|
||||
}
|
||||
|
||||
cliRef := c.clients[secRef]
|
||||
|
||||
if dependent == nil || len(cliRef.refs) == 0 {
|
||||
delete(c.clients, secRef)
|
||||
}
|
||||
}
|
||||
|
||||
func secretDataToGitHubClientConfig(data map[string][]byte) (*github.Config, error) {
|
||||
var (
|
||||
conf github.Config
|
||||
|
||||
err error
|
||||
)
|
||||
|
||||
conf.URL = string(data["github_url"])
|
||||
|
||||
conf.UploadURL = string(data["github_upload_url"])
|
||||
|
||||
conf.EnterpriseURL = string(data["github_enterprise_url"])
|
||||
|
||||
conf.RunnerGitHubURL = string(data["github_runner_url"])
|
||||
|
||||
conf.Token = string(data["github_token"])
|
||||
|
||||
appID := string(data["github_app_id"])
|
||||
|
||||
conf.AppID, err = strconv.ParseInt(appID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instID := string(data["github_app_installation_id"])
|
||||
|
||||
conf.AppInstallationID, err = strconv.ParseInt(instID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.AppPrivateKey = string(data["github_app_private_key"])
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
func refFromRunner(r *v1alpha1.Runner) *runnerOwnerRef {
|
||||
return &runnerOwnerRef{
|
||||
kind: r.Kind,
|
||||
ns: r.Namespace,
|
||||
name: r.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func refFromRunnerPod(po *corev1.Pod) *runnerOwnerRef {
|
||||
return &runnerOwnerRef{
|
||||
kind: po.Kind,
|
||||
ns: po.Namespace,
|
||||
name: po.Name,
|
||||
}
|
||||
}
|
||||
func refFromRunnerSet(rs *v1alpha1.RunnerSet) *runnerOwnerRef {
|
||||
return &runnerOwnerRef{
|
||||
kind: rs.Kind,
|
||||
ns: rs.Namespace,
|
||||
name: rs.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func refFromHorizontalRunnerAutoscaler(hra *v1alpha1.HorizontalRunnerAutoscaler) *runnerOwnerRef {
|
||||
return &runnerOwnerRef{
|
||||
kind: hra.Kind,
|
||||
ns: hra.Namespace,
|
||||
name: hra.Name,
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func newWorkGenericEphemeralVolume(t *testing.T, storageReq string) corev1.Volume {
|
||||
@@ -56,7 +58,7 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
@@ -125,6 +127,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
Name: "DOCKER_HOST",
|
||||
Value: "tcp://localhost:2376",
|
||||
@@ -198,7 +204,7 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
@@ -255,6 +261,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
@@ -276,7 +286,7 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
@@ -333,6 +343,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
@@ -515,7 +529,7 @@ func TestNewRunnerPod(t *testing.T) {
|
||||
for i := range testcases {
|
||||
tc := testcases[i]
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
got, err := newRunnerPod("runner", tc.template, tc.config, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL)
|
||||
got, err := newRunnerPod(tc.template, tc.config, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL, false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, got)
|
||||
})
|
||||
@@ -546,7 +560,7 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"pod-template-hash": "8857b86c7",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
@@ -624,6 +638,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
Name: "DOCKER_HOST",
|
||||
Value: "tcp://localhost:2376",
|
||||
@@ -703,7 +721,7 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"pod-template-hash": "8857b86c7",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
@@ -769,6 +787,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_NAME",
|
||||
Value: "runner",
|
||||
@@ -800,7 +822,7 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Labels: map[string]string{
|
||||
"actions-runner-controller/inject-registration-token": "true",
|
||||
"pod-template-hash": "8857b86c7",
|
||||
"runnerset-name": "runner",
|
||||
"actions-runner": "",
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
@@ -866,6 +888,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
Name: "RUNNER_EPHEMERAL",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_NAME",
|
||||
Value: "runner",
|
||||
@@ -1105,13 +1131,20 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||
|
||||
for i := range testcases {
|
||||
tc := testcases[i]
|
||||
|
||||
rr := &testResourceReader{
|
||||
objects: map[types.NamespacedName]client.Object{},
|
||||
}
|
||||
|
||||
multiClient := NewMultiGitHubClient(rr, &github.Client{GithubBaseURL: githubBaseURL})
|
||||
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
r := &RunnerReconciler{
|
||||
RunnerImage: defaultRunnerImage,
|
||||
RunnerImagePullSecrets: defaultRunnerImagePullSecrets,
|
||||
DockerImage: defaultDockerImage,
|
||||
DockerRegistryMirror: defaultDockerRegistryMirror,
|
||||
GitHubClient: &github.Client{GithubBaseURL: githubBaseURL},
|
||||
GitHubClient: multiClient,
|
||||
Scheme: scheme,
|
||||
}
|
||||
got, err := r.newPod(tc.runner)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/go-logr/logr"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
@@ -29,7 +28,7 @@ type PodRunnerTokenInjector struct {
|
||||
Name string
|
||||
Log logr.Logger
|
||||
Recorder record.EventRecorder
|
||||
GitHubClient *github.Client
|
||||
GitHubClient *MultiGitHubClient
|
||||
decoder *admission.Decoder
|
||||
}
|
||||
|
||||
@@ -66,7 +65,12 @@ func (t *PodRunnerTokenInjector) Handle(ctx context.Context, req admission.Reque
|
||||
return newEmptyResponse()
|
||||
}
|
||||
|
||||
rt, err := t.GitHubClient.GetRegistrationToken(context.Background(), enterprise, org, repo, pod.Name)
|
||||
ghc, err := t.GitHubClient.InitForRunnerPod(ctx, &pod)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
rt, err := ghc.GetRegistrationToken(context.Background(), enterprise, org, repo, pod.Name)
|
||||
if err != nil {
|
||||
t.Log.Error(err, "Failed to get new registration token")
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
|
||||
@@ -18,7 +18,10 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -33,10 +36,10 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -49,6 +52,8 @@ const (
|
||||
|
||||
EnvVarOrg = "RUNNER_ORG"
|
||||
EnvVarRepo = "RUNNER_REPO"
|
||||
EnvVarGroup = "RUNNER_GROUP"
|
||||
EnvVarLabels = "RUNNER_LABELS"
|
||||
EnvVarEnterprise = "RUNNER_ENTERPRISE"
|
||||
EnvVarEphemeral = "RUNNER_EPHEMERAL"
|
||||
EnvVarTrue = "true"
|
||||
@@ -60,7 +65,7 @@ type RunnerReconciler struct {
|
||||
Log logr.Logger
|
||||
Recorder record.EventRecorder
|
||||
Scheme *runtime.Scheme
|
||||
GitHubClient *github.Client
|
||||
GitHubClient *MultiGitHubClient
|
||||
RunnerImage string
|
||||
RunnerImagePullSecrets []string
|
||||
DockerImage string
|
||||
@@ -68,16 +73,20 @@ type RunnerReconciler struct {
|
||||
Name string
|
||||
RegistrationRecheckInterval time.Duration
|
||||
RegistrationRecheckJitter time.Duration
|
||||
|
||||
UnregistrationRetryDelay time.Duration
|
||||
UseRunnerStatusUpdateHook bool
|
||||
UnregistrationRetryDelay time.Duration
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners/finalizers,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
|
||||
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=create;delete;get
|
||||
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;delete;get
|
||||
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get
|
||||
|
||||
func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithValues("runner", req.NamespacedName)
|
||||
@@ -112,6 +121,9 @@ func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
|
||||
// Pod was not found
|
||||
return r.processRunnerDeletion(runner, ctx, log, nil)
|
||||
}
|
||||
|
||||
r.GitHubClient.DeinitForRunner(&runner)
|
||||
|
||||
return r.processRunnerDeletion(runner, ctx, log, &pod)
|
||||
}
|
||||
|
||||
@@ -131,7 +143,7 @@ func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
|
||||
|
||||
ready := runnerPodReady(&pod)
|
||||
|
||||
if runner.Status.Phase != phase || runner.Status.Ready != ready {
|
||||
if (runner.Status.Phase != phase || runner.Status.Ready != ready) && !r.UseRunnerStatusUpdateHook || runner.Status.Phase == "" && r.UseRunnerStatusUpdateHook {
|
||||
if pod.Status.Phase == corev1.PodRunning {
|
||||
// Seeing this message, you can expect the runner to become `Running` soon.
|
||||
log.V(1).Info(
|
||||
@@ -252,6 +264,96 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
needsServiceAccount := runner.Spec.ServiceAccountName == "" && (r.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes")
|
||||
if needsServiceAccount {
|
||||
serviceAccount := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: runner.ObjectMeta.Name,
|
||||
Namespace: runner.ObjectMeta.Namespace,
|
||||
},
|
||||
}
|
||||
if res := r.createObject(ctx, serviceAccount, serviceAccount.ObjectMeta, &runner, log); res != nil {
|
||||
return *res, nil
|
||||
}
|
||||
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
|
||||
if r.UseRunnerStatusUpdateHook {
|
||||
rules = append(rules, []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{"actions.summerwind.dev"},
|
||||
Resources: []string{"runners/status"},
|
||||
Verbs: []string{"get", "update", "patch"},
|
||||
ResourceNames: []string{runner.ObjectMeta.Name},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
if runner.Spec.ContainerMode == "kubernetes" {
|
||||
// Permissions based on https://github.com/actions/runner-container-hooks/blob/main/packages/k8s/README.md
|
||||
rules = append(rules, []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods"},
|
||||
Verbs: []string{"get", "list", "create", "delete"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods/exec"},
|
||||
Verbs: []string{"get", "create"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods/log"},
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"batch"},
|
||||
Resources: []string{"jobs"},
|
||||
Verbs: []string{"get", "list", "create", "delete"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"secrets"},
|
||||
Verbs: []string{"get", "list", "create", "delete"},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
role := &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: runner.ObjectMeta.Name,
|
||||
Namespace: runner.ObjectMeta.Namespace,
|
||||
},
|
||||
Rules: rules,
|
||||
}
|
||||
if res := r.createObject(ctx, role, role.ObjectMeta, &runner, log); res != nil {
|
||||
return *res, nil
|
||||
}
|
||||
|
||||
roleBinding := &rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: runner.ObjectMeta.Name,
|
||||
Namespace: runner.ObjectMeta.Namespace,
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: runner.ObjectMeta.Name,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: runner.ObjectMeta.Name,
|
||||
Namespace: runner.ObjectMeta.Namespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
if res := r.createObject(ctx, roleBinding, roleBinding.ObjectMeta, &runner, log); res != nil {
|
||||
return *res, nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.Create(ctx, &newPod); err != nil {
|
||||
if kerrors.IsAlreadyExists(err) {
|
||||
// Gracefully handle pod-already-exists errors due to informer cache delay.
|
||||
@@ -274,6 +376,27 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
func (r *RunnerReconciler) createObject(ctx context.Context, obj client.Object, meta metav1.ObjectMeta, runner *v1alpha1.Runner, log logr.Logger) *ctrl.Result {
|
||||
kind := strings.Split(reflect.TypeOf(obj).String(), ".")[1]
|
||||
if err := ctrl.SetControllerReference(runner, obj, r.Scheme); err != nil {
|
||||
log.Error(err, fmt.Sprintf("Could not add owner reference to %s %s. %s", kind, meta.Name, err.Error()))
|
||||
return &ctrl.Result{Requeue: true}
|
||||
}
|
||||
if err := r.Create(ctx, obj); err != nil {
|
||||
if kerrors.IsAlreadyExists(err) {
|
||||
log.Info(fmt.Sprintf("Failed to create %s %s as it already exists. Reusing existing %s", kind, meta.Name, kind))
|
||||
r.Recorder.Event(runner, corev1.EventTypeNormal, fmt.Sprintf("%sReused", kind), fmt.Sprintf("Reused %s '%s'", kind, meta.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Error(err, fmt.Sprintf("Retrying as failed to create %s %s resource", kind, meta.Name))
|
||||
return &ctrl.Result{Requeue: true}
|
||||
}
|
||||
r.Recorder.Event(runner, corev1.EventTypeNormal, fmt.Sprintf("%sCreated", kind), fmt.Sprintf("Created %s '%s'", kind, meta.Name))
|
||||
log.Info(fmt.Sprintf("Created %s", kind), "name", meta.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v1alpha1.Runner) (bool, error) {
|
||||
if runner.IsRegisterable() {
|
||||
return false, nil
|
||||
@@ -281,7 +404,12 @@ func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v
|
||||
|
||||
log := r.Log.WithValues("runner", runner.Name)
|
||||
|
||||
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||
ghc, err := r.GitHubClient.InitForRunner(ctx, &runner)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rt, err := ghc.GetRegistrationToken(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
|
||||
if err != nil {
|
||||
// An error can be a permanent, permission issue like the below:
|
||||
// POST https://api.github.com/enterprises/YOUR_ENTERPRISE/actions/runners/registration-token: 403 Resource not accessible by integration []
|
||||
@@ -321,6 +449,11 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
ghc, err := r.GitHubClient.InitForRunner(context.Background(), &runner)
|
||||
if err != nil {
|
||||
return corev1.Pod{}, err
|
||||
}
|
||||
|
||||
// This implies that...
|
||||
//
|
||||
// (1) We recreate the runner pod whenever the runner has changes in:
|
||||
@@ -344,7 +477,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
filterLabels(runner.ObjectMeta.Labels, LabelKeyRunnerTemplateHash),
|
||||
runner.ObjectMeta.Annotations,
|
||||
runner.Spec,
|
||||
r.GitHubClient.GithubBaseURL,
|
||||
ghc.GithubBaseURL,
|
||||
// Token change should trigger replacement.
|
||||
// We need to include this explicitly here because
|
||||
// runner.Spec does not contain the possibly updated token stored in the
|
||||
@@ -412,7 +545,17 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
template.Spec.SecurityContext = runner.Spec.SecurityContext
|
||||
template.Spec.EnableServiceLinks = runner.Spec.EnableServiceLinks
|
||||
|
||||
pod, err := newRunnerPod(runner.Name, template, runner.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, r.GitHubClient.GithubBaseURL)
|
||||
if runner.Spec.ContainerMode == "kubernetes" {
|
||||
workDir := runner.Spec.WorkDir
|
||||
if workDir == "" {
|
||||
workDir = "/runner/_work"
|
||||
}
|
||||
if err := applyWorkVolumeClaimTemplateToPod(&template, runner.Spec.WorkVolumeClaimTemplate, workDir); err != nil {
|
||||
return corev1.Pod{}, err
|
||||
}
|
||||
}
|
||||
|
||||
pod, err := newRunnerPodWithContainerMode(runner.Spec.ContainerMode, template, runner.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, ghc.GithubBaseURL, r.UseRunnerStatusUpdateHook)
|
||||
if err != nil {
|
||||
return pod, err
|
||||
}
|
||||
@@ -424,6 +567,9 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
// if operater provides a work volume mount, use that
|
||||
isPresent, _ := workVolumeMountPresent(runnerSpec.VolumeMounts)
|
||||
if isPresent {
|
||||
if runnerSpec.ContainerMode == "kubernetes" {
|
||||
return pod, errors.New("volume mount \"work\" should be specified by workVolumeClaimTemplate in container mode kubernetes")
|
||||
}
|
||||
// remove work volume since it will be provided from runnerSpec.Volumes
|
||||
// if we don't remove it here we would get a duplicate key error, i.e. two volumes named work
|
||||
_, index := workVolumeMountPresent(pod.Spec.Containers[0].VolumeMounts)
|
||||
@@ -437,6 +583,9 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
// if operator provides a work volume. use that
|
||||
isPresent, _ := workVolumePresent(runnerSpec.Volumes)
|
||||
if isPresent {
|
||||
if runnerSpec.ContainerMode == "kubernetes" {
|
||||
return pod, errors.New("volume \"work\" should be specified by workVolumeClaimTemplate in container mode kubernetes")
|
||||
}
|
||||
_, index := workVolumePresent(pod.Spec.Volumes)
|
||||
|
||||
// remove work volume since it will be provided from runnerSpec.Volumes
|
||||
@@ -446,6 +595,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
|
||||
pod.Spec.Volumes = append(pod.Spec.Volumes, runnerSpec.Volumes...)
|
||||
}
|
||||
|
||||
if len(runnerSpec.InitContainers) != 0 {
|
||||
pod.Spec.InitContainers = append(pod.Spec.InitContainers, runnerSpec.InitContainers...)
|
||||
}
|
||||
@@ -453,9 +603,13 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
if runnerSpec.NodeSelector != nil {
|
||||
pod.Spec.NodeSelector = runnerSpec.NodeSelector
|
||||
}
|
||||
|
||||
if runnerSpec.ServiceAccountName != "" {
|
||||
pod.Spec.ServiceAccountName = runnerSpec.ServiceAccountName
|
||||
} else if r.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes" {
|
||||
pod.Spec.ServiceAccountName = runner.ObjectMeta.Name
|
||||
}
|
||||
|
||||
if runnerSpec.AutomountServiceAccountToken != nil {
|
||||
pod.Spec.AutomountServiceAccountToken = runnerSpec.AutomountServiceAccountToken
|
||||
}
|
||||
@@ -476,6 +630,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
pod.Spec.Tolerations = runnerSpec.Tolerations
|
||||
}
|
||||
|
||||
if runnerSpec.PriorityClassName != "" {
|
||||
pod.Spec.PriorityClassName = runnerSpec.PriorityClassName
|
||||
}
|
||||
|
||||
if len(runnerSpec.TopologySpreadConstraints) != 0 {
|
||||
pod.Spec.TopologySpreadConstraints = runnerSpec.TopologySpreadConstraints
|
||||
}
|
||||
@@ -492,6 +650,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||
pod.Spec.HostAliases = runnerSpec.HostAliases
|
||||
}
|
||||
|
||||
if runnerSpec.DnsPolicy != "" {
|
||||
pod.Spec.DNSPolicy = runnerSpec.DnsPolicy
|
||||
}
|
||||
|
||||
if runnerSpec.DnsConfig != nil {
|
||||
pod.Spec.DNSConfig = runnerSpec.DnsConfig
|
||||
}
|
||||
@@ -526,7 +688,45 @@ func mutatePod(pod *corev1.Pod, token string) *corev1.Pod {
|
||||
return updated
|
||||
}
|
||||
|
||||
func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string) (corev1.Pod, error) {
|
||||
func runnerHookEnvs(pod *corev1.Pod) ([]corev1.EnvVar, error) {
|
||||
isRequireSameNode, err := isRequireSameNode(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []corev1.EnvVar{
|
||||
{
|
||||
Name: "ACTIONS_RUNNER_CONTAINER_HOOKS",
|
||||
Value: defaultRunnerHookPath,
|
||||
},
|
||||
{
|
||||
Name: "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER",
|
||||
Value: "true",
|
||||
},
|
||||
{
|
||||
Name: "ACTIONS_RUNNER_POD_NAME",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
FieldPath: "metadata.name",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ACTIONS_RUNNER_JOB_NAMESPACE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
FieldRef: &corev1.ObjectFieldSelector{
|
||||
FieldPath: "metadata.namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ACTIONS_RUNNER_REQUIRE_SAME_NODE",
|
||||
Value: strconv.FormatBool(isRequireSameNode),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string, useRunnerStatusUpdateHook bool) (corev1.Pod, error) {
|
||||
var (
|
||||
privileged bool = true
|
||||
dockerdInRunner bool = runnerSpec.DockerdWithinRunnerContainer != nil && *runnerSpec.DockerdWithinRunnerContainer
|
||||
@@ -535,11 +735,20 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
dockerdInRunnerPrivileged bool = dockerdInRunner
|
||||
)
|
||||
|
||||
if containerMode == "kubernetes" {
|
||||
dockerdInRunner = false
|
||||
dockerEnabled = false
|
||||
dockerdInRunnerPrivileged = false
|
||||
}
|
||||
|
||||
template = *template.DeepCopy()
|
||||
|
||||
// This label selector is used by default when rd.Spec.Selector is empty.
|
||||
template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunnerSetName, runnerName)
|
||||
template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunner, "")
|
||||
template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyPodMutation, LabelValuePodMutation)
|
||||
if runnerSpec.GitHubAPICredentialsFrom != nil {
|
||||
template.ObjectMeta.Annotations = CloneAndAddLabel(template.ObjectMeta.Annotations, annotationKeyGitHubAPICredsSecret, runnerSpec.GitHubAPICredentialsFrom.SecretRef.Name)
|
||||
}
|
||||
|
||||
workDir := runnerSpec.WorkDir
|
||||
if workDir == "" {
|
||||
@@ -569,11 +778,11 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
Value: runnerSpec.Enterprise,
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_LABELS",
|
||||
Name: EnvVarLabels,
|
||||
Value: strings.Join(runnerSpec.Labels, ","),
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_GROUP",
|
||||
Name: EnvVarGroup,
|
||||
Value: runnerSpec.Group,
|
||||
},
|
||||
{
|
||||
@@ -596,6 +805,10 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
Name: EnvVarEphemeral,
|
||||
Value: fmt.Sprintf("%v", ephemeral),
|
||||
},
|
||||
{
|
||||
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||
Value: fmt.Sprintf("%v", useRunnerStatusUpdateHook),
|
||||
},
|
||||
}
|
||||
|
||||
var seLinuxOptions *corev1.SELinuxOptions
|
||||
@@ -621,6 +834,17 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
}
|
||||
}
|
||||
|
||||
if containerMode == "kubernetes" {
|
||||
if dockerdContainer != nil {
|
||||
template.Spec.Containers = append(template.Spec.Containers[:dockerdContainerIndex], template.Spec.Containers[dockerdContainerIndex+1:]...)
|
||||
}
|
||||
if dockerdContainerIndex < runnerContainerIndex {
|
||||
runnerContainerIndex--
|
||||
}
|
||||
dockerdContainer = nil
|
||||
dockerdContainerIndex = -1
|
||||
}
|
||||
|
||||
if runnerContainer == nil {
|
||||
runnerContainerIndex = -1
|
||||
runnerContainer = &corev1.Container{
|
||||
@@ -651,6 +875,13 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
}
|
||||
|
||||
runnerContainer.Env = append(runnerContainer.Env, env...)
|
||||
if containerMode == "kubernetes" {
|
||||
hookEnvs, err := runnerHookEnvs(&template)
|
||||
if err != nil {
|
||||
return corev1.Pod{}, err
|
||||
}
|
||||
runnerContainer.Env = append(runnerContainer.Env, hookEnvs...)
|
||||
}
|
||||
|
||||
if runnerContainer.SecurityContext == nil {
|
||||
runnerContainer.SecurityContext = &corev1.SecurityContext{}
|
||||
@@ -841,6 +1072,74 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
},
|
||||
}...)
|
||||
|
||||
// This let dockerd to create container's network interface to have the specified MTU.
|
||||
// In other words, this is for setting com.docker.network.driver.mtu in the docker bridge options.
|
||||
// You can see the options by running `docker network inspect bridge`, where you will see something like the below when spec.dockerMTU=1400:
|
||||
//
|
||||
// "Options": {
|
||||
// "com.docker.network.bridge.default_bridge": "true",
|
||||
// "com.docker.network.bridge.enable_icc": "true",
|
||||
// "com.docker.network.bridge.enable_ip_masquerade": "true",
|
||||
// "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
|
||||
// "com.docker.network.bridge.name": "docker0",
|
||||
// "com.docker.network.driver.mtu": "1400"
|
||||
// },
|
||||
//
|
||||
// See e.g. https://forums.docker.com/t/changing-mtu-value/74114 and https://mlohr.com/docker-mtu/ for more details.
|
||||
//
|
||||
// Note though, this doesn't immediately affect docker0's MTU, and the MTU of the docker network created with docker-create-network:
|
||||
// You can verity that by running `ip link` within the containers:
|
||||
//
|
||||
// # ip link
|
||||
// 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
|
||||
// link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
// 2: eth0@if1118: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
|
||||
// link/ether c2:dd:e6:66:8e:8b brd ff:ff:ff:ff:ff:ff
|
||||
// 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
|
||||
// link/ether 02:42:ab:1c:83:69 brd ff:ff:ff:ff:ff:ff
|
||||
// 4: br-c5bf6c172bd7: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
|
||||
// link/ether 02:42:e2:91:13:1e brd ff:ff:ff:ff:ff:ff
|
||||
//
|
||||
// br-c5bf6c172bd7 is the interface that corresponds to the docker network created with docker-create-network.
|
||||
// We have another ARC feature to inherit the host's MTU to the docker networks:
|
||||
// https://github.com/actions-runner-controller/actions-runner-controller/pull/1201
|
||||
//
|
||||
// docker's MTU is updated to the specified MTU once any container is created.
|
||||
// You can verity that by running a random container from within the runner or dockerd containers:
|
||||
//
|
||||
// / # docker run -d busybox sh -c 'sleep 10'
|
||||
// e848e6acd6404ca0199e4d9c5ef485d88c974ddfb7aaf2359c66811f68cf5e42
|
||||
//
|
||||
// You'll now see the veth767f1a5@if7 got created with the MTU inherited by dockerd:
|
||||
//
|
||||
// / # ip link
|
||||
// 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
|
||||
// link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
// 2: eth0@if1118: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
|
||||
// link/ether c2:dd:e6:66:8e:8b brd ff:ff:ff:ff:ff:ff
|
||||
// 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc noqueue state UP
|
||||
// link/ether 02:42:ab:1c:83:69 brd ff:ff:ff:ff:ff:ff
|
||||
// 4: br-c5bf6c172bd7: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
|
||||
// link/ether 02:42:e2:91:13:1e brd ff:ff:ff:ff:ff:ff
|
||||
// 8: veth767f1a5@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1400 qdisc noqueue master docker0 state UP
|
||||
// link/ether 82:d5:08:28:d8:98 brd ff:ff:ff:ff:ff:ff
|
||||
//
|
||||
// # After 10 seconds sleep, you can see the container stops and the veth767f1a5@if7 interface got deleted:
|
||||
//
|
||||
// / # ip link
|
||||
// 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
|
||||
// link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
// 2: eth0@if1118: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
|
||||
// link/ether c2:dd:e6:66:8e:8b brd ff:ff:ff:ff:ff:ff
|
||||
// 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
|
||||
// link/ether 02:42:ab:1c:83:69 brd ff:ff:ff:ff:ff:ff
|
||||
// 4: br-c5bf6c172bd7: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
|
||||
// link/ether 02:42:e2:91:13:1e brd ff:ff:ff:ff:ff:ff
|
||||
//
|
||||
// See https://github.com/moby/moby/issues/26382#issuecomment-246906331 for reference.
|
||||
//
|
||||
// Probably we'd better infer DockerMTU from the host's primary interface's MTU and docker0's MTU?
|
||||
// That's another story- if you want it, please start a thread in GitHub Discussions!
|
||||
dockerdContainer.Args = append(dockerdContainer.Args,
|
||||
"--mtu",
|
||||
fmt.Sprintf("%d", *runnerSpec.DockerMTU),
|
||||
@@ -875,6 +1174,10 @@ func newRunnerPod(runnerName string, template corev1.Pod, runnerSpec v1alpha1.Ru
|
||||
return *pod, nil
|
||||
}
|
||||
|
||||
func newRunnerPod(template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string, useRunnerStatusUpdateHookEphemeralRole bool) (corev1.Pod, error) {
|
||||
return newRunnerPodWithContainerMode("", template, runnerSpec, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL, useRunnerStatusUpdateHookEphemeralRole)
|
||||
}
|
||||
|
||||
func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
name := "runner-controller"
|
||||
if r.Name != "" {
|
||||
@@ -937,3 +1240,61 @@ func workVolumeMountPresent(items []corev1.VolumeMount) (bool, int) {
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func applyWorkVolumeClaimTemplateToPod(pod *corev1.Pod, workVolumeClaimTemplate *v1alpha1.WorkVolumeClaimTemplate, workDir string) error {
|
||||
if workVolumeClaimTemplate == nil {
|
||||
return errors.New("work volume claim template must be specified in container mode kubernetes")
|
||||
}
|
||||
for i := range pod.Spec.Volumes {
|
||||
if pod.Spec.Volumes[i].Name == "work" {
|
||||
return fmt.Errorf("Work volume should not be specified in container mode kubernetes. workVolumeClaimTemplate field should be used instead.")
|
||||
}
|
||||
}
|
||||
pod.Spec.Volumes = append(pod.Spec.Volumes, workVolumeClaimTemplate.V1Volume())
|
||||
|
||||
var runnerContainer *corev1.Container
|
||||
for i := range pod.Spec.Containers {
|
||||
if pod.Spec.Containers[i].Name == "runner" {
|
||||
runnerContainer = &pod.Spec.Containers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if runnerContainer == nil {
|
||||
return fmt.Errorf("runner container is not present when applying work volume claim template")
|
||||
}
|
||||
|
||||
if isPresent, _ := workVolumeMountPresent(runnerContainer.VolumeMounts); isPresent {
|
||||
return fmt.Errorf("volume mount \"work\" should not be present on the runner container in container mode kubernetes")
|
||||
}
|
||||
|
||||
runnerContainer.VolumeMounts = append(runnerContainer.VolumeMounts, workVolumeClaimTemplate.V1VolumeMount(workDir))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRequireSameNode specifies for the runner in kubernetes mode wether it should
|
||||
// schedule jobs to the same node where the runner is
|
||||
//
|
||||
// This function should only be called in containerMode: kubernetes
|
||||
func isRequireSameNode(pod *corev1.Pod) (bool, error) {
|
||||
isPresent, index := workVolumePresent(pod.Spec.Volumes)
|
||||
if !isPresent {
|
||||
return true, errors.New("internal error: work volume mount must exist in containerMode: kubernetes")
|
||||
}
|
||||
|
||||
if pod.Spec.Volumes[index].Ephemeral == nil || pod.Spec.Volumes[index].Ephemeral.VolumeClaimTemplate == nil {
|
||||
return true, errors.New("containerMode: kubernetes should have pod.Spec.Volumes[].Ephemeral.VolumeClaimTemplate set")
|
||||
}
|
||||
|
||||
for _, accessMode := range pod.Spec.Volumes[index].Ephemeral.VolumeClaimTemplate.Spec.AccessModes {
|
||||
switch accessMode {
|
||||
case corev1.ReadWriteOnce:
|
||||
return true, nil
|
||||
case corev1.ReadWriteMany:
|
||||
default:
|
||||
return true, errors.New("actions-runner-controller supports ReadWriteOnce and ReadWriteMany modes only")
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
"github.com/go-logr/logr"
|
||||
gogithub "github.com/google/go-github/v39/github"
|
||||
gogithub "github.com/google/go-github/v47/github"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
@@ -98,11 +98,27 @@ func ensureRunnerUnregistration(ctx context.Context, retryDelay time.Duration, l
|
||||
// If it's already unregistered in the previous reconcilation loop,
|
||||
// you can safely assume that it won't get registered again so it's safe to delete the runner pod.
|
||||
log.Info("Runner pod is marked as already unregistered.")
|
||||
} else if runnerID == nil {
|
||||
} else if runnerID == nil && !runnerPodOrContainerIsStopped(pod) && !podConditionTransitionTimeAfter(pod, corev1.PodReady, registrationTimeout) {
|
||||
log.Info(
|
||||
"Unregistration started before runner ID is assigned. " +
|
||||
"Perhaps the runner pod was terminated by anyone other than ARC? Was it OOM killed? " +
|
||||
"Unregistration started before runner obtains ID. Waiting for the regisration timeout to elapse, or the runner to obtain ID, or the runner pod to stop",
|
||||
"registrationTimeout", registrationTimeout,
|
||||
)
|
||||
return &ctrl.Result{RequeueAfter: retryDelay}, nil
|
||||
} else if runnerID == nil && runnerPodOrContainerIsStopped(pod) {
|
||||
log.Info(
|
||||
"Unregistration started before runner ID is assigned and the runner stopped before obtaining ID within registration timeout. "+
|
||||
"Perhaps the runner successfully ran the job and stopped normally before the runner ID becomes visible via GitHub API? "+
|
||||
"Perhaps the runner pod was terminated by anyone other than ARC? Was it OOM killed? "+
|
||||
"Marking unregistration as completed anyway because there's nothing ARC can do.",
|
||||
"registrationTimeout", registrationTimeout,
|
||||
)
|
||||
} else if runnerID == nil && podConditionTransitionTimeAfter(pod, corev1.PodReady, registrationTimeout) {
|
||||
log.Info(
|
||||
"Unregistration started before runner ID is assigned and the runner was unable to obtain ID within registration timeout. "+
|
||||
"Perhaps the runner has communication issue, or a firewall egress rule is dropping traffic to GitHub API, or GitHub API is unavailable? "+
|
||||
"Marking unregistration as completed anyway because there's nothing ARC can do. "+
|
||||
"This may result in in cancelling the job depending on your terminationGracePeriodSeconds and RUNNER_GRACEFUL_STOP_TIMEOUT settings.",
|
||||
"registrationTimeout", registrationTimeout,
|
||||
)
|
||||
} else if pod != nil && runnerPodOrContainerIsStopped(pod) {
|
||||
// If it's an ephemeral runner with the actions/runner container exited with 0,
|
||||
@@ -151,7 +167,10 @@ func ensureRunnerUnregistration(ctx context.Context, retryDelay time.Duration, l
|
||||
|
||||
log.V(1).Info("Failed to unregister runner before deleting the pod.", "error", err)
|
||||
|
||||
var runnerBusy bool
|
||||
var (
|
||||
runnerBusy bool
|
||||
runnerUnregistrationFailureMessage string
|
||||
)
|
||||
|
||||
errRes := &gogithub.ErrorResponse{}
|
||||
if errors.As(err, &errRes) {
|
||||
@@ -173,6 +192,7 @@ func ensureRunnerUnregistration(ctx context.Context, retryDelay time.Duration, l
|
||||
}
|
||||
|
||||
runnerBusy = errRes.Response.StatusCode == 422
|
||||
runnerUnregistrationFailureMessage = errRes.Message
|
||||
|
||||
if runnerBusy && code != nil {
|
||||
log.V(2).Info("Runner container has already stopped but the unregistration attempt failed. "+
|
||||
@@ -187,6 +207,11 @@ func ensureRunnerUnregistration(ctx context.Context, retryDelay time.Duration, l
|
||||
}
|
||||
|
||||
if runnerBusy {
|
||||
_, err := annotatePodOnce(ctx, c, log, pod, AnnotationKeyUnregistrationFailureMessage, runnerUnregistrationFailureMessage)
|
||||
if err != nil {
|
||||
return &ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// We want to prevent spamming the deletion attemps but returning ctrl.Result with RequeueAfter doesn't
|
||||
// work as the reconcilation can happen earlier due to pod status update.
|
||||
// For ephemeral runners, we can expect it to stop and unregister itself on completion.
|
||||
@@ -342,21 +367,22 @@ func setRunnerEnv(pod *corev1.Pod, key, value string) {
|
||||
// Case 1. (true, nil) when it has successfully unregistered the runner.
|
||||
// Case 2. (false, nil) when (2-1.) the runner has been already unregistered OR (2-2.) the runner will never be created OR (2-3.) the runner is not created yet and it is about to be registered(hence we couldn't see it's existence from GitHub Actions API yet)
|
||||
// Case 3. (false, err) when it postponed unregistration due to the runner being busy, or it tried to unregister the runner but failed due to
|
||||
// an error returned by GitHub API.
|
||||
//
|
||||
// an error returned by GitHub API.
|
||||
//
|
||||
// When the returned values is "Case 2. (false, nil)", the caller must handle the three possible sub-cases appropriately.
|
||||
// In other words, all those three sub-cases cannot be distinguished by this function alone.
|
||||
//
|
||||
// - Case "2-1." can happen when e.g. ARC has successfully unregistered in a previous reconcilation loop or it was an ephemeral runner that finished it's job run(an ephemeral runner is designed to stop after a job run).
|
||||
// You'd need to maintain the runner state(i.e. if it's already unregistered or not) somewhere,
|
||||
// so that you can either not call this function at all if the runner state says it's already unregistered, or determine that it's case "2-1." when you got (false, nil).
|
||||
// - Case "2-1." can happen when e.g. ARC has successfully unregistered in a previous reconcilation loop or it was an ephemeral runner that finished it's job run(an ephemeral runner is designed to stop after a job run).
|
||||
// You'd need to maintain the runner state(i.e. if it's already unregistered or not) somewhere,
|
||||
// so that you can either not call this function at all if the runner state says it's already unregistered, or determine that it's case "2-1." when you got (false, nil).
|
||||
//
|
||||
// - Case "2-2." can happen when e.g. the runner registration token was somehow broken so that `config.sh` within the runner container was never meant to succeed.
|
||||
// Waiting and retrying forever on this case is not a solution, because `config.sh` won't succeed with a wrong token hence the runner gets stuck in this state forever.
|
||||
// There isn't a perfect solution to this, but a practical workaround would be implement a "grace period" in the caller side.
|
||||
// - Case "2-2." can happen when e.g. the runner registration token was somehow broken so that `config.sh` within the runner container was never meant to succeed.
|
||||
// Waiting and retrying forever on this case is not a solution, because `config.sh` won't succeed with a wrong token hence the runner gets stuck in this state forever.
|
||||
// There isn't a perfect solution to this, but a practical workaround would be implement a "grace period" in the caller side.
|
||||
//
|
||||
// - Case "2-3." can happen when e.g. ARC recreated an ephemral runner pod in a previous reconcilation loop and then it was requested to delete the runner before the runner comes up.
|
||||
// If handled inappropriately, this can cause a race condition betweeen a deletion of the runner pod and GitHub scheduling a workflow job onto the runner.
|
||||
// - Case "2-3." can happen when e.g. ARC recreated an ephemral runner pod in a previous reconcilation loop and then it was requested to delete the runner before the runner comes up.
|
||||
// If handled inappropriately, this can cause a race condition betweeen a deletion of the runner pod and GitHub scheduling a workflow job onto the runner.
|
||||
//
|
||||
// Once successfully detected case "2-1." or "2-2.", you can safely delete the runner pod because you know that the runner won't come back
|
||||
// as long as you recreate the runner pod.
|
||||
|
||||
@@ -20,19 +20,21 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
arcv1alpha1 "github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||
|
||||
"github.com/actions-runner-controller/actions-runner-controller/github"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// RunnerPodReconciler reconciles a Runner object
|
||||
@@ -41,7 +43,7 @@ type RunnerPodReconciler struct {
|
||||
Log logr.Logger
|
||||
Recorder record.EventRecorder
|
||||
Scheme *runtime.Scheme
|
||||
GitHubClient *github.Client
|
||||
GitHubClient *MultiGitHubClient
|
||||
Name string
|
||||
RegistrationRecheckInterval time.Duration
|
||||
RegistrationRecheckJitter time.Duration
|
||||
@@ -50,6 +52,7 @@ type RunnerPodReconciler struct {
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
|
||||
|
||||
func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
@@ -60,8 +63,11 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
_, isRunnerPod := runnerPod.Labels[LabelKeyRunnerSetName]
|
||||
if !isRunnerPod {
|
||||
_, isRunnerPod := runnerPod.Labels[LabelKeyRunner]
|
||||
_, isRunnerSetPod := runnerPod.Labels[LabelKeyRunnerSetName]
|
||||
_, isRunnerDeploymentPod := runnerPod.Labels[LabelKeyRunnerDeploymentName]
|
||||
|
||||
if !isRunnerPod && !isRunnerSetPod && !isRunnerDeploymentPod {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -77,6 +83,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
}
|
||||
|
||||
var enterprise, org, repo string
|
||||
var isContainerMode bool
|
||||
|
||||
for _, e := range envvars {
|
||||
switch e.Name {
|
||||
@@ -86,13 +93,25 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
org = e.Value
|
||||
case EnvVarRepo:
|
||||
repo = e.Value
|
||||
case "ACTIONS_RUNNER_CONTAINER_HOOKS":
|
||||
isContainerMode = true
|
||||
}
|
||||
}
|
||||
|
||||
ghc, err := r.GitHubClient.InitForRunnerPod(ctx, &runnerPod)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if runnerPod.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
finalizers, added := addFinalizer(runnerPod.ObjectMeta.Finalizers, runnerPodFinalizerName)
|
||||
|
||||
if added {
|
||||
var cleanupFinalizersAdded bool
|
||||
if isContainerMode {
|
||||
finalizers, cleanupFinalizersAdded = addFinalizer(finalizers, runnerLinkedResourcesFinalizerName)
|
||||
}
|
||||
|
||||
if added || cleanupFinalizersAdded {
|
||||
newRunner := runnerPod.DeepCopy()
|
||||
newRunner.ObjectMeta.Finalizers = finalizers
|
||||
|
||||
@@ -108,13 +127,49 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
} else {
|
||||
log.V(2).Info("Seen deletion-timestamp is already set")
|
||||
|
||||
// Mark the parent Runner resource for deletion before deleting this runner pod from the cluster.
|
||||
// Otherwise the runner controller can recreate the runner pod thinking it has not created any runner pod yet.
|
||||
var (
|
||||
key = types.NamespacedName{Namespace: runnerPod.Namespace, Name: runnerPod.Name}
|
||||
runner arcv1alpha1.Runner
|
||||
)
|
||||
if err := r.Get(ctx, key, &runner); err == nil {
|
||||
if runner.Name != "" && runner.DeletionTimestamp == nil {
|
||||
log.Info("This runner pod seems to have been deleted directly, bypassing the parent Runner resource. Marking the runner for deletion to not let it recreate this pod.")
|
||||
if err := r.Delete(ctx, &runner); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if finalizers, removed := removeFinalizer(runnerPod.ObjectMeta.Finalizers, runnerLinkedResourcesFinalizerName); removed {
|
||||
if err := r.cleanupRunnerLinkedPods(ctx, &runnerPod, log); err != nil {
|
||||
log.Info("Runner-linked pods clean up that has failed due to an error. If this persists, please manually remove the runner-linked pods to unblock ARC", "err", err.Error())
|
||||
return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil
|
||||
}
|
||||
if err := r.cleanupRunnerLinkedSecrets(ctx, &runnerPod, log); err != nil {
|
||||
log.Info("Runner-linked secrets clean up that has failed due to an error. If this persists, please manually remove the runner-linked secrets to unblock ARC", "err", err.Error())
|
||||
return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil
|
||||
}
|
||||
patchedPod := runnerPod.DeepCopy()
|
||||
patchedPod.ObjectMeta.Finalizers = finalizers
|
||||
|
||||
if err := r.Patch(ctx, patchedPod, client.MergeFrom(&runnerPod)); err != nil {
|
||||
log.Error(err, "Failed to update runner for finalizer linked resources removal")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Otherwise the subsequent patch request can revive the removed finalizer and it will trigger a unnecessary reconcilation
|
||||
runnerPod = *patchedPod
|
||||
}
|
||||
|
||||
finalizers, removed := removeFinalizer(runnerPod.ObjectMeta.Finalizers, runnerPodFinalizerName)
|
||||
|
||||
if removed {
|
||||
// In a standard scenario, the upstream controller, like runnerset-controller, ensures this runner to be gracefully stopped before the deletion timestamp is set.
|
||||
// But for the case that the user manually deleted it for whatever reason,
|
||||
// we have to ensure it to gracefully stop now.
|
||||
updatedPod, res, err := tickRunnerGracefulStop(ctx, r.unregistrationRetryDelay(), log, r.GitHubClient, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
updatedPod, res, err := tickRunnerGracefulStop(ctx, r.unregistrationRetryDelay(), log, ghc, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
if res != nil {
|
||||
return *res, err
|
||||
}
|
||||
@@ -130,6 +185,8 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
|
||||
log.V(2).Info("Removed finalizer")
|
||||
|
||||
r.GitHubClient.DeinitForRunnerPod(updatedPod)
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
@@ -168,7 +225,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
po, res, err := ensureRunnerPodRegistered(ctx, log, r.GitHubClient, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
po, res, err := ensureRunnerPodRegistered(ctx, log, ghc, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
if res != nil {
|
||||
return *res, err
|
||||
}
|
||||
@@ -182,7 +239,7 @@ func (r *RunnerPodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
|
||||
//
|
||||
// In a standard scenario, ARC starts the unregistration process before marking the pod for deletion at all,
|
||||
// so that it isn't subject to terminationGracePeriod and can safely take hours to finish it's work.
|
||||
_, res, err := tickRunnerGracefulStop(ctx, r.unregistrationRetryDelay(), log, r.GitHubClient, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
_, res, err := tickRunnerGracefulStop(ctx, r.unregistrationRetryDelay(), log, ghc, r.Client, enterprise, org, repo, runnerPod.Name, &runnerPod)
|
||||
if res != nil {
|
||||
return *res, err
|
||||
}
|
||||
@@ -222,3 +279,93 @@ func (r *RunnerPodReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Named(name).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *RunnerPodReconciler) cleanupRunnerLinkedPods(ctx context.Context, pod *corev1.Pod, log logr.Logger) error {
|
||||
var runnerLinkedPodList corev1.PodList
|
||||
if err := r.List(ctx, &runnerLinkedPodList, client.InNamespace(pod.Namespace), client.MatchingLabels(
|
||||
map[string]string{
|
||||
"runner-pod": pod.ObjectMeta.Name,
|
||||
},
|
||||
)); err != nil {
|
||||
return fmt.Errorf("failed to list runner-linked pods: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
errs []error
|
||||
)
|
||||
for _, p := range runnerLinkedPodList.Items {
|
||||
if !p.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
p := p
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := r.Delete(ctx, &p); err != nil {
|
||||
if kerrors.IsNotFound(err) || kerrors.IsGone(err) {
|
||||
return
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("delete pod %q error: %v", p.ObjectMeta.Name, err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
log.Error(err, "failed to remove runner-linked pod")
|
||||
}
|
||||
return errors.New("failed to remove some runner linked pods")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RunnerPodReconciler) cleanupRunnerLinkedSecrets(ctx context.Context, pod *corev1.Pod, log logr.Logger) error {
|
||||
log.V(2).Info("Listing runner-linked secrets to be deleted", "ns", pod.Namespace)
|
||||
|
||||
var runnerLinkedSecretList corev1.SecretList
|
||||
if err := r.List(ctx, &runnerLinkedSecretList, client.InNamespace(pod.Namespace), client.MatchingLabels(
|
||||
map[string]string{
|
||||
"runner-pod": pod.ObjectMeta.Name,
|
||||
},
|
||||
)); err != nil {
|
||||
return fmt.Errorf("failed to list runner-linked secrets: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
errs []error
|
||||
)
|
||||
for _, s := range runnerLinkedSecretList.Items {
|
||||
if !s.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
s := s
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := r.Delete(ctx, &s); err != nil {
|
||||
if kerrors.IsNotFound(err) || kerrors.IsGone(err) {
|
||||
return
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("delete secret %q error: %v", s.ObjectMeta.Name, err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
log.Error(err, "failed to remove runner-linked secret")
|
||||
}
|
||||
return errors.New("failed to remove some runner linked secrets")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user