Compare commits

...

25 Commits

Author SHA1 Message Date
Bassem Dghaidi
f49d08e4bc Update 2022-12-05-adding-labels-k8s-resources.md (#2420) 2023-03-17 06:39:56 -04:00
Tingluo Huang
064039afc0 Ignore extra dind container when contaerinMode.type=dind. (#2418) 2023-03-17 09:26:51 +01:00
Nikola Jokic
e5d8d65396 Introduce ADR change for adding labels to our resources (#2407)
Co-authored-by: Bassem Dghaidi <568794+Link-@users.noreply.github.com>
2023-03-16 11:02:42 -04:00
Bassem Dghaidi
c465ace8fb Update the values.yaml sample for improved clarity (#2416) 2023-03-16 11:02:18 -04:00
Tingluo Huang
34f3878829 Fix helm chart rendering errors. (#2414) 2023-03-16 09:21:43 -04:00
Tingluo Huang
44c3931d8e Adding e2e workflows to test dind, kube mode and proxy (#2412) 2023-03-15 12:17:11 -04:00
Tingluo Huang
08acb1b831 Get RunnerScaleSet based on both RunnerGroupId and Name. (#2413) 2023-03-15 11:10:09 -04:00
Tingluo Huang
40811ebe0e Support the controller to watching a single namespace. (#2374) 2023-03-14 10:52:25 -04:00
github-actions[bot]
3417c5a3a8 Update runner to version 2.303.0 (#2411)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-03-14 15:41:03 +01:00
Bassem Dghaidi
172faa883c Fix GITHUB_TOKEN permissions (#2410) 2023-03-14 10:38:04 -04:00
Tingluo Huang
9e6c7d019f Delay role/rolebinding creation to gha-runner-scale-set installation time (#2363) 2023-03-14 09:45:44 -04:00
Bassem Dghaidi
9fbcafa703 Fix canary image tag name (#2409) 2023-03-14 09:29:10 -04:00
Tingluo Huang
2bf83d0d7f Remove list/watch secrets permission from the manager cluster role. (#2276) 2023-03-14 09:23:14 -04:00
Bassem Dghaidi
19d30dea5f Add docker buildx pre-requisites (#2408) 2023-03-14 09:22:38 -04:00
Bassem Dghaidi
6c66c1633f Prevent releases on wrong tag name (#2406) 2023-03-14 09:13:25 -04:00
Bassem Dghaidi
e55708588b Add gha-runner-scale-set-controller canary build (#2405) 2023-03-14 09:12:53 -04:00
Tingluo Huang
261d4371b5 Update E2E test workflow. (#2395) 2023-03-14 09:00:07 -04:00
Tingluo Huang
bd9f32e354 Create separate chart validation workflow for gha-* charts. (#2393)
Co-authored-by: Nikola Jokic <jokicnikola07@gmail.com>
2023-03-13 12:44:54 -04:00
Nikola Jokic
babbfc77d5 Surface EphemeralRunnerSet stats to AutoscalingRunnerSet (#2382) 2023-03-13 16:16:28 +01:00
Bassem Dghaidi
322df79617 Delete renovate.json5 (#2397) 2023-03-13 08:39:07 -04:00
Bassem Dghaidi
1c7c6639ed Fix wrong file name in the workflow (#2394) 2023-03-13 06:56:21 -04:00
Hamish Forbes
bcaac39a2e feat(actionsmetrics): Add owner and workflow_name labels to workflow job metrics (#2225) 2023-03-13 10:50:36 +09:00
Milas Bowman
af625dd1cb Upgrade to Docker Engine v20.10.23 (#2328)
Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2023-03-13 10:29:40 +09:00
Bassem Dghaidi
44969659df Add upgrade steps (#2392)
Co-authored-by: Nikola Jokic <jokicnikola07@gmail.com>
2023-03-10 12:14:00 -05:00
Nikola Jokic
a5f98dea75 Refactor main.go and introduce make run-scaleset to be able to run manager locally (#2337) 2023-03-10 18:05:51 +01:00
84 changed files with 3301 additions and 712 deletions

View File

@@ -1,45 +0,0 @@
name: 'E2E ARC Test Action'
description: 'Includes common arc installation, setup and test file run'
inputs:
github-token:
description: 'JWT generated with Github App inputs'
required: true
config-url:
description: "URL of the repo, org or enterprise where the runner scale sets will be registered"
required: true
docker-image-repo:
description: "Local docker image repo for testing"
required: true
docker-image-tag:
description: "Tag of ARC Docker image for testing"
required: true
runs:
using: "composite"
steps:
- name: Install ARC
run: helm install arc --namespace "arc-systems" --create-namespace --set image.tag=${{ inputs.docker-image-tag }} --set image.repository=${{ inputs.docker-image-repo }} ./charts/gha-runner-scale-set-controller
shell: bash
- name: Get datetime
# We are using this value further in the runner installation to avoid runner name collision that are a risk with hard coded values.
# A datetime including the 3 nanoseconds are a good option for this and also adds to readability and runner sorting if needed.
run: echo "DATE_TIME=$(date +'%Y-%m-%d-%H-%M-%S-%3N')" >> $GITHUB_ENV
shell: bash
- name: Install runners
run: |
helm install "arc-runner-${{ env.DATE_TIME }}" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="${{ inputs.config-url }}" \
--set githubConfigSecret.github_token="${{ inputs.github-token }}" \
./charts/gha-runner-scale-set \
--debug
kubectl get pods -A
shell: bash
- name: Test ARC scales pods up and down
run: |
export GITHUB_TOKEN="${{ inputs.github-token }}"
export DATE_TIME="${{ env.DATE_TIME }}"
go test ./test_e2e_arc -v
shell: bash

View File

@@ -0,0 +1,64 @@
name: 'Setup ARC E2E Test Action'
description: 'Build controller image, create kind cluster, load the image, and exchange ARC configure token.'
inputs:
github-app-id:
description: 'GitHub App Id for exchange access token'
required: true
github-app-pk:
description: "GitHub App private key for exchange access token"
required: true
github-app-org:
description: 'The organization the GitHub App has installed on'
required: true
docker-image-name:
description: "Local docker image name for building"
required: true
docker-image-tag:
description: "Tag of ARC Docker image for building"
required: true
outputs:
token:
description: 'Token to use for configure ARC'
value: ${{steps.config-token.outputs.token}}
runs:
using: "composite"
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
# Pinning v0.9.1 for Buildx and BuildKit v0.10.6
# BuildKit v0.11 which has a bug causing intermittent
# failures pushing images to GHCR
version: v0.9.1
driver-opts: image=moby/buildkit:v0.10.6
- name: Build controller image
uses: docker/build-push-action@v3
with:
file: Dockerfile
platforms: linux/amd64
load: true
build-args: |
DOCKER_IMAGE_NAME=${{inputs.docker-image-name}}
VERSION=${{inputs.docker-image-tag}}
tags: |
${{inputs.docker-image-name}}:${{inputs.docker-image-tag}}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create minikube cluster and load image
shell: bash
run: |
minikube start
minikube image load ${{inputs.docker-image-name}}:${{inputs.docker-image-tag}}
- name: Get configure token
id: config-token
uses: peter-murray/workflow-application-token-action@8e1ba3bf1619726336414f1014e37f17fbadf1db
with:
application_id: ${{ inputs.github-app-id }}
application_private_key: ${{ inputs.github-app-pk }}
organization: ${{ inputs.github-app-org }}

View File

@@ -1,43 +0,0 @@
{
"extends": ["config:base"],
"labels": ["dependencies"],
"packageRules": [
{
// automatically merge an update of runner
"matchPackageNames": ["actions/runner"],
"extractVersion": "^v(?<version>.*)$",
"automerge": true
}
],
"regexManagers": [
{
// use https://github.com/actions/runner/releases
"fileMatch": [
".github/workflows/runners.yaml"
],
"matchStrings": ["RUNNER_VERSION: +(?<currentValue>.*?)\\n"],
"depNameTemplate": "actions/runner",
"datasourceTemplate": "github-releases"
},
{
"fileMatch": [
"runner/Makefile",
"Makefile"
],
"matchStrings": ["RUNNER_VERSION \\?= +(?<currentValue>.*?)\\n"],
"depNameTemplate": "actions/runner",
"datasourceTemplate": "github-releases"
},
{
"fileMatch": [
"runner/actions-runner.ubuntu-20.04.dockerfile",
"runner/actions-runner.ubuntu-22.04.dockerfile",
"runner/actions-runner-dind.ubuntu-20.04.dockerfile",
"runner/actions-runner-dind-rootless.ubuntu-20.04.dockerfile"
],
"matchStrings": ["RUNNER_VERSION=+(?<currentValue>.*?)\\n"],
"depNameTemplate": "actions/runner",
"datasourceTemplate": "github-releases"
}
]
}

View File

@@ -5,47 +5,730 @@ on:
branches: branches:
- master - master
pull_request: pull_request:
branches:
- master
workflow_dispatch: workflow_dispatch:
inputs:
target_org:
description: The org of the test repository.
required: true
default: actions-runner-controller
target_repo:
description: The repository to install the ARC.
required: true
default: arc_e2e_test_dummy
env: env:
TARGET_ORG: actions-runner-controller TARGET_ORG: actions-runner-controller
CLUSTER_NAME: e2e-test TARGET_REPO: arc_e2e_test_dummy
RUNNER_VERSION: 2.302.1 IMAGE_NAME: "arc-test-image"
IMAGE_REPO: "test/test-image" IMAGE_VERSION: "dev"
jobs: jobs:
setup-steps: default-setup:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
env:
WORKFLOW_FILE: "arc-test-workflow.yaml"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Add env variables
- name: Resolve inputs
id: resolved_inputs
run: | run: |
TAG=$(echo "0.0.$GITHUB_SHA") TARGET_ORG="${{env.TARGET_ORG}}"
echo "TAG=$TAG" >> $GITHUB_ENV TARGET_REPO="${{env.TARGET_REPO}}"
echo "IMAGE=$IMAGE_REPO:$TAG" >> $GITHUB_ENV if [ ! -z "${{inputs.target_org}}" ]; then
- name: Set up Docker Buildx TARGET_ORG="${{inputs.target_org}}"
uses: docker/setup-buildx-action@v2 fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with: with:
version: latest github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
- name: Docker Build Test Image github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: | run: |
DOCKER_CLI_EXPERIMENTAL=enabled DOCKER_BUILDKIT=1 docker buildx build --build-arg RUNNER_VERSION=$RUNNER_VERSION --build-arg TAG=$TAG -t $IMAGE . --load helm install arc \
- name: Create Kind cluster --namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: | run: |
PATH=$(go env GOPATH)/bin:$PATH ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
kind create cluster --name $CLUSTER_NAME helm install "$ARC_NAME" \
- name: Load Image to Kind Cluster --namespace "arc-runners" \
run: kind load docker-image $IMAGE --name $CLUSTER_NAME --create-namespace \
- name: Get Token --set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
id: get_workflow_token --set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
uses: peter-murray/workflow-application-token-action@8e1ba3bf1619726336414f1014e37f17fbadf1db ./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{env.WORKFLOW_FILE}}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF
single-namespace-setup:
runs-on: ubuntu-latest
env:
WORKFLOW_FILE: "arc-test-workflow.yaml"
steps:
- uses: actions/checkout@v3
- name: Resolve inputs
id: resolved_inputs
run: |
TARGET_ORG="${{env.TARGET_ORG}}"
TARGET_REPO="${{env.TARGET_REPO}}"
if [ ! -z "${{inputs.target_org}}" ]; then
TARGET_ORG="${{inputs.target_org}}"
fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with: with:
application_id: ${{ secrets.ACTIONS_ACCESS_APP_ID }} github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
application_private_key: ${{ secrets.ACTIONS_ACCESS_PK }} github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
organization: ${{ env.TARGET_ORG }} github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
- uses: ./.github/actions/e2e-arc-test docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: |
kubectl create namespace arc-runners
helm install arc \
--namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
--set flags.watchSingleNamespace=arc-runners \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: |
ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
helm install "$ARC_NAME" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
--set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{env.WORKFLOW_FILE}}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF
dind-mode-setup:
runs-on: ubuntu-latest
env:
WORKFLOW_FILE: arc-test-dind-workflow.yaml
steps:
- uses: actions/checkout@v3
- name: Resolve inputs
id: resolved_inputs
run: |
TARGET_ORG="${{env.TARGET_ORG}}"
TARGET_REPO="${{env.TARGET_REPO}}"
if [ ! -z "${{inputs.target_org}}" ]; then
TARGET_ORG="${{inputs.target_org}}"
fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with: with:
github-token: ${{ steps.get_workflow_token.outputs.token }} github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
config-url: "https://github.com/actions-runner-controller/arc_e2e_test_dummy" github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
docker-image-repo: $IMAGE_REPO github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
docker-image-tag: $TAG docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: |
helm install arc \
--namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: |
ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
helm install "$ARC_NAME" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
--set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
--set containerMode.type="dind" \
./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{env.WORKFLOW_FILE}}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF
kubernetes-mode-setup:
runs-on: ubuntu-latest
env:
WORKFLOW_FILE: "arc-test-kubernetes-workflow.yaml"
steps:
- uses: actions/checkout@v3
- name: Resolve inputs
id: resolved_inputs
run: |
TARGET_ORG="${{env.TARGET_ORG}}"
TARGET_REPO="${{env.TARGET_REPO}}"
if [ ! -z "${{inputs.target_org}}" ]; then
TARGET_ORG="${{inputs.target_org}}"
fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with:
github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: |
helm install arc \
--namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: |
echo "Install openebs/dynamic-localpv-provisioner"
helm repo add openebs https://openebs.github.io/charts
helm repo update
helm install openebs openebs/openebs -n openebs --create-namespace
ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
helm install "$ARC_NAME" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
--set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
--set containerMode.type="kubernetes" \
--set containerMode.kubernetesModeWorkVolumeClaim.accessModes={"ReadWriteOnce"} \
--set containerMode.kubernetesModeWorkVolumeClaim.storageClassName="openebs-hostpath" \
--set containerMode.kubernetesModeWorkVolumeClaim.resources.requests.storage="1Gi" \
./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{env.WORKFLOW_FILE}}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF
auth-proxy-setup:
runs-on: ubuntu-latest
env:
WORKFLOW_FILE: "arc-test-workflow.yaml"
steps:
- uses: actions/checkout@v3
- name: Resolve inputs
id: resolved_inputs
run: |
TARGET_ORG="${{env.TARGET_ORG}}"
TARGET_REPO="${{env.TARGET_REPO}}"
if [ ! -z "${{inputs.target_org}}" ]; then
TARGET_ORG="${{inputs.target_org}}"
fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with:
github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: |
helm install arc \
--namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: |
docker run -d \
--name squid \
--publish 3128:3128 \
huangtingluo/squid-proxy:latest
kubectl create namespace arc-runners
kubectl create secret generic proxy-auth \
--namespace=arc-runners \
--from-literal=username=github \
--from-literal=password='actions'
ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
helm install "$ARC_NAME" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
--set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
--set proxy.https.url="http://host.minikube.internal:3128" \
--set proxy.https.credentialSecretRef="proxy-auth" \
--set "proxy.noProxy[0]=10.96.0.1:443" \
./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{env.WORKFLOW_FILE}}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF
anonymous-proxy-setup:
runs-on: ubuntu-latest
env:
WORKFLOW_FILE: "arc-test-workflow.yaml"
steps:
- uses: actions/checkout@v3
- name: Resolve inputs
id: resolved_inputs
run: |
TARGET_ORG="${{env.TARGET_ORG}}"
TARGET_REPO="${{env.TARGET_REPO}}"
if [ ! -z "${{inputs.target_org}}" ]; then
TARGET_ORG="${{inputs.target_org}}"
fi
if [ ! -z "${{inputs.target_repo}}" ]; then
TARGET_REPO="${{inputs.target_repo}}"
fi
echo "TARGET_ORG=$TARGET_ORG" >> $GITHUB_OUTPUT
echo "TARGET_REPO=$TARGET_REPO" >> $GITHUB_OUTPUT
- uses: ./.github/actions/setup-arc-e2e
id: setup
with:
github-app-id: ${{secrets.ACTIONS_ACCESS_APP_ID}}
github-app-pk: ${{secrets.ACTIONS_ACCESS_PK}}
github-app-org: ${{steps.resolved_inputs.outputs.TARGET_ORG}}
docker-image-name: ${{env.IMAGE_NAME}}
docker-image-tag: ${{env.IMAGE_VERSION}}
- name: Install gha-runner-scale-set-controller
id: install_arc_controller
run: |
helm install arc \
--namespace "arc-systems" \
--create-namespace \
--set image.repository=${{ env.IMAGE_NAME }} \
--set image.tag=${{ env.IMAGE_VERSION }} \
./charts/gha-runner-scale-set-controller \
--debug
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for controller pod with label app.kubernetes.io/name=gha-runner-scale-set-controller"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l app.kubernetes.io/name=gha-runner-scale-set-controller
kubectl get pod -n arc-systems
kubectl describe deployment arc-gha-runner-scale-set-controller -n arc-systems
- name: Install gha-runner-scale-set
id: install_arc
run: |
docker run -d \
--name squid \
--publish 3128:3128 \
ubuntu/squid:latest
ARC_NAME=arc-runner-${{github.job}}-$(date +'%M-%S')-$(($RANDOM % 100 + 1))
helm install "$ARC_NAME" \
--namespace "arc-runners" \
--create-namespace \
--set githubConfigUrl="https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}" \
--set githubConfigSecret.github_token="${{ steps.setup.outputs.token }}" \
--set proxy.https.url="http://host.minikube.internal:3128" \
--set "proxy.noProxy[0]=10.96.0.1:443" \
./charts/gha-runner-scale-set \
--debug
echo "ARC_NAME=$ARC_NAME" >> $GITHUB_OUTPUT
count=0
while true; do
POD_NAME=$(kubectl get pods -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME -o name)
if [ -n "$POD_NAME" ]; then
echo "Pod found: $POD_NAME"
break
fi
if [ "$count" -ge 10 ]; then
echo "Timeout waiting for listener pod with label auto-scaling-runner-set-name=$ARC_NAME"
exit 1
fi
sleep 1
done
kubectl wait --timeout=30s --for=condition=ready pod -n arc-systems -l auto-scaling-runner-set-name=$ARC_NAME
kubectl get pod -n arc-systems
- name: Test ARC scales pods up and down
id: test
run: |
export GITHUB_TOKEN="${{ steps.setup.outputs.token }}"
export ARC_NAME="${{ steps.install_arc.outputs.ARC_NAME }}"
export WORKFLOW_FILE="${{ env.WORKFLOW_FILE }}"
go test ./test_e2e_arc -v
- name: Uninstall gha-runner-scale-set
if: always() && steps.install_arc.outcome == 'success'
run: |
helm uninstall ${{ steps.install_arc.outputs.ARC_NAME }} --namespace arc-runners
kubectl wait --timeout=10s --for=delete AutoScalingRunnerSet -n demo -l app.kubernetes.io/instance=${{ steps.install_arc.outputs.ARC_NAME }}
- name: Dump gha-runner-scale-set-controller logs
if: always() && steps.install_arc_controller.outcome == 'success'
run: |
kubectl logs deployment/arc-gha-runner-scale-set-controller -n arc-systems
- name: Job summary
if: always() && steps.install_arc.outcome == 'success'
run: |
cat <<-EOF > $GITHUB_STEP_SUMMARY
| **Outcome** | ${{ steps.test.outcome }} |
|----------------|--------------------------------------------- |
| **References** | [Test workflow runs](https://github.com/${{ steps.resolved_inputs.outputs.TARGET_ORG }}/${{steps.resolved_inputs.outputs.TARGET_REPO}}/actions/workflows/${{ env.WORKFLOW_FILE }}) |
EOF

View File

@@ -20,4 +20,4 @@ jobs:
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:
only-new-issues: true only-new-issues: true
version: v1.49.0 version: v1.51.1

View File

@@ -29,6 +29,10 @@ jobs:
release-controller: release-controller:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
# gha-runner-scale-set has its own release workflow.
# We don't want to publish a new actions-runner-controller image
# we release gha-runner-scale-set.
if: ${{ !startsWith(github.event.inputs.release_tag_name, 'gha-runner-scale-set-') }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@@ -8,35 +8,47 @@ on:
- master - master
paths-ignore: paths-ignore:
- '**.md' - '**.md'
- '.github/actions/**'
- '.github/ISSUE_TEMPLATE/**' - '.github/ISSUE_TEMPLATE/**'
- '.github/workflows/validate-chart.yaml' - '.github/workflows/e2e-test-dispatch-workflow.yaml'
- '.github/workflows/publish-chart.yaml' - '.github/workflows/e2e-test-linux-vm.yaml'
- '.github/workflows/publish-arc.yaml' - '.github/workflows/publish-arc.yaml'
- '.github/workflows/runners.yaml' - '.github/workflows/publish-chart.yaml'
- '.github/workflows/validate-entrypoint.yaml' - '.github/workflows/publish-runner-scale-set.yaml'
- '.github/renovate.*' - '.github/workflows/release-runners.yaml'
- '.github/workflows/run-codeql.yaml'
- '.github/workflows/run-first-interaction.yaml'
- '.github/workflows/run-stale.yaml'
- '.github/workflows/update-runners.yaml'
- '.github/workflows/validate-arc.yaml'
- '.github/workflows/validate-chart.yaml'
- '.github/workflows/validate-gha-chart.yaml'
- '.github/workflows/validate-runners.yaml'
- '.github/dependabot.yml'
- '.github/RELEASE_NOTE_TEMPLATE.md'
- 'runner/**' - 'runner/**'
- '.gitignore' - '.gitignore'
- 'PROJECT' - 'PROJECT'
- 'LICENSE' - 'LICENSE'
- 'Makefile' - 'Makefile'
env:
# Safeguard to prevent pushing images to registeries after build
PUSH_TO_REGISTRIES: true
TARGET_ORG: actions-runner-controller
TARGET_REPO: actions-runner-controller
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps
permissions: permissions:
contents: read contents: read
packages: write
env:
# Safeguard to prevent pushing images to registeries after build
PUSH_TO_REGISTRIES: true
jobs: jobs:
canary-build: legacy-canary-build:
name: Build and Publish Canary Image name: Build and Publish Legacy Canary Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
TARGET_ORG: actions-runner-controller
TARGET_REPO: actions-runner-controller
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -68,3 +80,50 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Status:**" >> $GITHUB_STEP_SUMMARY echo "**Status:**" >> $GITHUB_STEP_SUMMARY
echo "[https://github.com/actions-runner-controller/releases/actions/workflows/publish-canary.yaml](https://github.com/actions-runner-controller/releases/actions/workflows/publish-canary.yaml)" >> $GITHUB_STEP_SUMMARY echo "[https://github.com/actions-runner-controller/releases/actions/workflows/publish-canary.yaml](https://github.com/actions-runner-controller/releases/actions/workflows/publish-canary.yaml)" >> $GITHUB_STEP_SUMMARY
canary-build:
name: Build and Publish gha-runner-scale-set-controller Canary Image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Normalization is needed because upper case characters are not allowed in the repository name
# and the short sha is needed for image tagging
- name: Resolve parameters
id: resolve_parameters
run: |
echo "INFO: Resolving short sha"
echo "short_sha=$(git rev-parse --short ${{ github.ref }})" >> $GITHUB_OUTPUT
echo "INFO: Normalizing repository name (lowercase)"
echo "repository_owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: latest
# Unstable builds - run at your own risk
- name: Build and Push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
build-args: VERSION=canary-"${{ github.ref }}"
push: ${{ env.PUSH_TO_REGISTRIES }}
tags: |
ghcr.io/${{ steps.resolve_parameters.outputs.repository_owner }}/gha-runner-scale-set-controller:canary
ghcr.io/${{ steps.resolve_parameters.outputs.repository_owner }}/gha-runner-scale-set-controller:canary-${{ steps.resolve_parameters.outputs.short_sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -17,7 +17,7 @@ env:
PUSH_TO_REGISTRIES: true PUSH_TO_REGISTRIES: true
TARGET_ORG: actions-runner-controller TARGET_ORG: actions-runner-controller
TARGET_WORKFLOW: release-runners.yaml TARGET_WORKFLOW: release-runners.yaml
DOCKER_VERSION: 20.10.21 DOCKER_VERSION: 20.10.23
RUNNER_CONTAINER_HOOKS_VERSION: 0.2.0 RUNNER_CONTAINER_HOOKS_VERSION: 0.2.0
jobs: jobs:

View File

@@ -77,6 +77,7 @@ jobs:
permissions: permissions:
pull-requests: write pull-requests: write
contents: write contents: write
actions: write
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
CURRENT_VERSION: ${{ needs.check_versions.outputs.current_version }} CURRENT_VERSION: ${{ needs.check_versions.outputs.current_version }}
@@ -93,7 +94,7 @@ jobs:
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/Makefile sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" runner/Makefile
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" Makefile sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" Makefile
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" test/e2e/e2e_test.go sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" test/e2e/e2e_test.go
sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" .github/workflows/e2e_test_linux_vm.yaml sed -i "s/$CURRENT_VERSION/$LATEST_VERSION/g" .github/workflows/e2e-test-linux-vm.yaml
- name: Commit changes - name: Commit changes
run: | run: |

View File

@@ -9,12 +9,16 @@ on:
- '.github/workflows/validate-chart.yaml' - '.github/workflows/validate-chart.yaml'
- '!charts/actions-runner-controller/docs/**' - '!charts/actions-runner-controller/docs/**'
- '!**.md' - '!**.md'
- '!charts/gha-runner-scale-set-controller/**'
- '!charts/gha-runner-scale-set/**'
push: push:
paths: paths:
- 'charts/**' - 'charts/**'
- '.github/workflows/validate-chart.yaml' - '.github/workflows/validate-chart.yaml'
- '!charts/actions-runner-controller/docs/**' - '!charts/actions-runner-controller/docs/**'
- '!**.md' - '!**.md'
- '!charts/gha-runner-scale-set-controller/**'
- '!charts/gha-runner-scale-set/**'
workflow_dispatch: workflow_dispatch:
env: env:
KUBE_SCORE_VERSION: 1.10.0 KUBE_SCORE_VERSION: 1.10.0

View File

@@ -0,0 +1,134 @@
name: Validate Helm Chart (gha-runner-scale-set-controller and gha-runner-scale-set)
on:
pull_request:
branches:
- master
paths:
- 'charts/**'
- '.github/workflows/validate-gha-chart.yaml'
- '!charts/actions-runner-controller/**'
- '!**.md'
push:
paths:
- 'charts/**'
- '.github/workflows/validate-gha-chart.yaml'
- '!charts/actions-runner-controller/**'
- '!**.md'
workflow_dispatch:
env:
KUBE_SCORE_VERSION: 1.16.1
HELM_VERSION: v3.8.0
permissions:
contents: read
jobs:
validate-chart:
name: Lint Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
# Using https://github.com/Azure/setup-helm/releases/tag/v3.5
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78
with:
version: ${{ env.HELM_VERSION }}
- name: Set up kube-score
run: |
wget https://github.com/zegl/kube-score/releases/download/v${{ env.KUBE_SCORE_VERSION }}/kube-score_${{ env.KUBE_SCORE_VERSION }}_linux_amd64 -O kube-score
chmod 755 kube-score
- name: Kube-score generated manifests
run: helm template --values charts/.ci/values-kube-score.yaml charts/* | ./kube-score score -
--ignore-test pod-networkpolicy
--ignore-test deployment-has-poddisruptionbudget
--ignore-test deployment-has-host-podantiaffinity
--ignore-test container-security-context
--ignore-test pod-probes
--ignore-test container-image-tag
--enable-optional-test container-security-context-privileged
--enable-optional-test container-security-context-readonlyrootfilesystem
# python is a requirement for the chart-testing action below (supports yamllint among other tests)
- uses: actions/setup-python@v4
with:
python-version: '3.7'
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.3.1
- name: Set up latest version chart-testing
run: |
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt update
sudo apt install goreleaser
git clone https://github.com/helm/chart-testing
cd chart-testing
unset CT_CONFIG_DIR
goreleaser build --clean --skip-validate
./dist/chart-testing_linux_amd64_v1/ct version
echo 'Adding ct directory to PATH...'
echo "$RUNNER_TEMP/chart-testing/dist/chart-testing_linux_amd64_v1" >> "$GITHUB_PATH"
echo 'Setting CT_CONFIG_DIR...'
echo "CT_CONFIG_DIR=$RUNNER_TEMP/chart-testing/etc" >> "$GITHUB_ENV"
working-directory: ${{ runner.temp }}
- name: Run chart-testing (list-changed)
id: list-changed
run: |
ct version
changed=$(ct list-changed --config charts/.ci/ct-config-gha.yaml)
if [[ -n "$changed" ]]; then
echo "::set-output name=changed::true"
fi
- name: Run chart-testing (lint)
run: |
ct lint --config charts/.ci/ct-config-gha.yaml
- name: Set up docker buildx
uses: docker/setup-buildx-action@v2
if: steps.list-changed.outputs.changed == 'true'
with:
version: latest
- name: Build controller image
uses: docker/build-push-action@v3
if: steps.list-changed.outputs.changed == 'true'
with:
file: Dockerfile
platforms: linux/amd64
load: true
build-args: |
DOCKER_IMAGE_NAME=test-arc
VERSION=dev
tags: |
test-arc:dev
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Create kind cluster
uses: helm/kind-action@v1.4.0
if: steps.list-changed.outputs.changed == 'true'
with:
cluster_name: chart-testing
- name: Load image into cluster
if: steps.list-changed.outputs.changed == 'true'
run: |
export DOCKER_IMAGE_NAME=test-arc
export VERSION=dev
export IMG_RESULT=load
make docker-buildx
kind load docker-image test-arc:dev --name chart-testing
- name: Run chart-testing (install)
if: steps.list-changed.outputs.changed == 'true'
run: |
ct install --config charts/.ci/ct-config-gha.yaml

View File

@@ -5,7 +5,7 @@ else
endif endif
DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1) DOCKER_USER ?= $(shell echo ${DOCKER_IMAGE_NAME} | cut -d / -f1)
VERSION ?= dev VERSION ?= dev
RUNNER_VERSION ?= 2.302.1 RUNNER_VERSION ?= 2.303.0
TARGETPLATFORM ?= $(shell arch) TARGETPLATFORM ?= $(shell arch)
RUNNER_NAME ?= ${DOCKER_USER}/actions-runner RUNNER_NAME ?= ${DOCKER_USER}/actions-runner
RUNNER_TAG ?= ${VERSION} RUNNER_TAG ?= ${VERSION}
@@ -92,9 +92,14 @@ manager: generate fmt vet
run: generate fmt vet manifests run: generate fmt vet manifests
go run ./main.go go run ./main.go
run-scaleset: generate fmt vet
CONTROLLER_MANAGER_POD_NAMESPACE=default \
CONTROLLER_MANAGER_CONTAINER_IMAGE="${DOCKER_IMAGE_NAME}:${VERSION}" \
go run ./main.go --auto-scaling-runner-set-only
# Install CRDs into a cluster # Install CRDs into a cluster
install: manifests install: manifests
kustomize build config/crd | kubectl apply -f - kustomize build config/crd | kubectl apply --server-side -f -
# Uninstall CRDs from a cluster # Uninstall CRDs from a cluster
uninstall: manifests uninstall: manifests
@@ -103,7 +108,7 @@ uninstall: manifests
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config # Deploy controller in the configured Kubernetes cluster in ~/.kube/config
deploy: manifests deploy: manifests
cd config/manager && kustomize edit set image controller=${DOCKER_IMAGE_NAME}:${VERSION} cd config/manager && kustomize edit set image controller=${DOCKER_IMAGE_NAME}:${VERSION}
kustomize build config/default | kubectl apply -f - kustomize build config/default | kubectl apply --server-side -f -
# Generate manifests e.g. CRD, RBAC etc. # Generate manifests e.g. CRD, RBAC etc.
manifests: manifests-gen-crds chart-crds manifests: manifests-gen-crds chart-crds

View File

@@ -1,4 +1,5 @@
# ADR 0001: Produce the runner image for the scaleset client # ADR 2022-10-17: Produce the runner image for the scaleset client
**Date**: 2022-10-17 **Date**: 2022-10-17
**Status**: Done **Status**: Done
@@ -7,6 +8,7 @@
We aim to provide an similar experience (as close as possible) between self-hosted and GitHub-hosted runners. To achieve this, we are making the following changes to align our self-hosted runner container image with the Ubuntu runners managed by GitHub. We aim to provide an similar experience (as close as possible) between self-hosted and GitHub-hosted runners. To achieve this, we are making the following changes to align our self-hosted runner container image with the Ubuntu runners managed by GitHub.
Here are the changes: Here are the changes:
- We created a USER `runner(1001)` and a GROUP `docker(123)` - We created a USER `runner(1001)` and a GROUP `docker(123)`
- `sudo` has been on the image and the `runner` will be a passwordless sudoer. - `sudo` has been on the image and the `runner` will be a passwordless sudoer.
- The runner binary was placed placed under `/home/runner/` and launched using `/home/runner/run.sh` - The runner binary was placed placed under `/home/runner/` and launched using `/home/runner/run.sh`
@@ -18,31 +20,33 @@ The latest Dockerfile can be found at: https://github.com/actions/runner/blob/ma
# Context # Context
user can bring their own runner images, the contract we have are: users can bring their own runner images, the contract we require is:
- It must have a runner binary under /actions-runner (/actions-runner/run.sh exists)
- The WORKDIR is set to /actions-runner
- If the user inside the container is root, the ENV RUNNER_ALLOW_RUNASROOT should be set to 1
The existing ARC runner images will not work with the new ARC mode out-of-box for the following reason: - It must have a runner binary under `/actions-runner` i.e. `/actions-runner/run.sh` exists
- The `WORKDIR` is set to `/actions-runner`
- If the user inside the container is root, the environment variable `RUNNER_ALLOW_RUNASROOT` should be set to `1`
- The current runner image requires caller to pass runner configure info, ex: URL and Config Token The existing [ARC runner images](https://github.com/orgs/actions-runner-controller/packages?tab=packages&q=actions-runner) will not work with the new ARC mode out-of-box for the following reason:
- The current runner image has the runner binary under /runner
- The current runner image requires the caller to pass runner configuration info, ex: URL and Config Token
- The current runner image has the runner binary under `/runner` which violates the contract described above
- The current runner image requires a special entrypoint script in order to work around some volume mount limitation for setting up DinD. - The current runner image requires a special entrypoint script in order to work around some volume mount limitation for setting up DinD.
However, since we expose the raw runner Pod spec to our user, advanced user can modify the helm values.yaml to make everything lines up properly. Since we expose the raw runner PodSpec to our end users, they can modify the helm `values.yaml` to adjust the runner container to their needs.
# Guiding Principles # Guiding Principles
- Build image is separated in two stages. - Build image is separated in two stages.
## The first stage (build) ## The first stage (build)
- Reuses the same base image, so it is faster to build. - Reuses the same base image, so it is faster to build.
- Installs utilities needed to download assets (runner and runner-container-hooks). - Installs utilities needed to download assets (`runner` and `runner-container-hooks`).
- Downloads the runner and stores it into `/actions-runner` directory. - Downloads the runner and stores it into `/actions-runner` directory.
- Downloads the runner-container-hooks and stores it into `/actions-runner/k8s` directory. - Downloads the runner-container-hooks and stores it into `/actions-runner/k8s` directory.
- You can use build arguments to control the runner version, the target platform and runner container hooks version. - You can use build arguments to control the runner version, the target platform and runner container hooks version.
Preview: Preview (the published runner image might vary):
```Dockerfile ```Dockerfile
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
@@ -64,6 +68,7 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c
``` ```
## The main image: ## The main image:
- Copies assets from the build stage to `/actions-runner` - Copies assets from the build stage to `/actions-runner`
- Does not provide an entrypoint. The entrypoint should be set within the container definition. - Does not provide an entrypoint. The entrypoint should be set within the container definition.
@@ -77,6 +82,7 @@ COPY --from=build /actions-runner .
``` ```
## Example of pod spec with the init container copying assets ## Example of pod spec with the init container copying assets
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
@@ -84,20 +90,20 @@ metadata:
name: <name> name: <name>
spec: spec:
containers: containers:
- name: runner
image: <image>
command: ["/runner/run.sh"]
volumeMounts:
- name: runner - name: runner
mountPath: /runner image: <image>
command: ["/runner/run.sh"]
volumeMounts:
- name: runner
mountPath: /runner
initContainers: initContainers:
- name: setup - name: setup
image: <image> image: <image>
command: ["sh", "-c", "cp -r /actions-runner/* /runner/"] command: ["sh", "-c", "cp -r /actions-runner/* /runner/"]
volumeMounts: volumeMounts:
- name: runner - name: runner
mountPath: /runner mountPath: /runner
volumes: volumes:
- name: runner - name: runner
emptyDir: {} emptyDir: {}
``` ```

View File

@@ -1,4 +1,4 @@
# ADR 0003: Lifetime of RunnerScaleSet on Service # ADR 2022-10-27: Lifetime of RunnerScaleSet on Service
**Date**: 2022-10-27 **Date**: 2022-10-27
@@ -12,8 +12,9 @@ The `RunnerScaleSet` object will represent a set of homogeneous self-hosted runn
A `RunnerScaleSet` client (ARC) needs to communicate with the Actions service via HTTP long-poll in a certain protocol to get a workflow job successfully landed on one of its homogeneous self-hosted runners. A `RunnerScaleSet` client (ARC) needs to communicate with the Actions service via HTTP long-poll in a certain protocol to get a workflow job successfully landed on one of its homogeneous self-hosted runners.
In this ADR, I want to discuss the following within the context of actions-runner-controller's new scaling mode: In this ADR, we discuss the following within the context of actions-runner-controller's new scaling mode:
- Who and how to create a RunnerScaleSet on the service?
- Who and how to create a RunnerScaleSet on the service?
- Who and how to delete a RunnerScaleSet on the service? - Who and how to delete a RunnerScaleSet on the service?
- What will happen to all the runners and jobs when the deletion happens? - What will happen to all the runners and jobs when the deletion happens?
@@ -30,18 +31,19 @@ In this ADR, I want to discuss the following within the context of actions-runne
- When the user patch existing `AutoScalingRunnerSet`'s RunnerScaleSet related properly, ex: `runnerGroupName`, `runnerWorkDir`, the controller needs to make an HTTP PATCH call to the `_apis/runtime/runnerscalesets/2` endpoint in order to update the object on the service. - When the user patch existing `AutoScalingRunnerSet`'s RunnerScaleSet related properly, ex: `runnerGroupName`, `runnerWorkDir`, the controller needs to make an HTTP PATCH call to the `_apis/runtime/runnerscalesets/2` endpoint in order to update the object on the service.
- We will put the deployed `AutoScalingRunnerSet` resource in an error state when the user tries to patch the resource with a different `githubConfigUrl` - We will put the deployed `AutoScalingRunnerSet` resource in an error state when the user tries to patch the resource with a different `githubConfigUrl`
> Basically, you can't move a deployed `AutoScalingRunnerSet` across GitHub entity, repoA->repoB, repoA->OrgC, etc. > Basically, you can't move a deployed `AutoScalingRunnerSet` across GitHub entity, repoA->repoB, repoA->OrgC, etc.
> We evaluated blocking the change before instead of erroring at runtime and that we decided not to go down this route because it forces us to re-introduce admission webhooks (require cert-manager). > We evaluated blocking the change before instead of erroring at runtime and that we decided not to go down this route because it forces us to re-introduce admission webhooks (require cert-manager).
## RunnerScaleSet deletion ## RunnerScaleSet deletion
- `AutoScalingRunnerSet` custom resource controller will delete the `RunnerScaleSet` object in the Actions service on any `AutoScalingRunnerSet` resource deletion. - `AutoScalingRunnerSet` custom resource controller will delete the `RunnerScaleSet` object in the Actions service on any `AutoScalingRunnerSet` resource deletion.
> `AutoScalingRunnerSet` deletion will contain several steps: > `AutoScalingRunnerSet` deletion will contain several steps:
> - Stop the listener app so no more new jobs coming and no more scaling up/down. >
> - Request scale down to 0 > - Stop the listener app so no more new jobs coming and no more scaling up/down.
> - Force stop all runners > - Request scale down to 0
> - Wait for the scale down to 0 > - Force stop all runners
> - Delete the `RunnerScaleSet` object from service via REST API > - Wait for the scale down to 0
> - Delete the `RunnerScaleSet` object from service via REST API
- The deletion is via REST API on Actions service `DELETE _apis/runtime/runnerscalesets/1` - The deletion is via REST API on Actions service `DELETE _apis/runtime/runnerscalesets/1`
- The deletion needs to use the runner registration token (admin). - The deletion needs to use the runner registration token (admin).

View File

@@ -1,4 +1,5 @@
# ADR 0004: Technical detail about actions-runner-controller repository transfer # ADR 2022-11-04: Technical detail about actions-runner-controller repository transfer
**Date**: 2022-11-04 **Date**: 2022-11-04
**Status**: Done **Status**: Done
@@ -8,16 +9,17 @@
As part of ARC Private Beta: Repository Migration & Open Sourcing Process, we have decided to transfer the current [actions-runner-controller repository](https://github.com/actions-runner-controller/actions-runner-controller) into the [Actions org](https://github.com/actions). As part of ARC Private Beta: Repository Migration & Open Sourcing Process, we have decided to transfer the current [actions-runner-controller repository](https://github.com/actions-runner-controller/actions-runner-controller) into the [Actions org](https://github.com/actions).
**Goals:** **Goals:**
- A clear signal that GitHub will start taking over ARC and provide support. - A clear signal that GitHub will start taking over ARC and provide support.
- Since we are going to deprecate the existing auto-scale mode in ARC at some point, we want to have a clear separation between the legacy mode (not supported) and the new mode (supported). - Since we are going to deprecate the existing auto-scale mode in ARC at some point, we want to have a clear separation between the legacy mode (not supported) and the new mode (supported).
- Avoid disrupting users as much as we can, existing ARC users will not notice any difference after the repository transfer, they can keep upgrading to the newer version of ARC and keep using the legacy mode. - Avoid disrupting users as much as we can, existing ARC users will not notice any difference after the repository transfer, they can keep upgrading to the newer version of ARC and keep using the legacy mode.
**Challenges** **Challenges**
- The original creator's name (`summerwind`) is all over the place, including some critical parts of ARC:
- The k8s user resource API's full name is `actions.summerwind.dev/v1alpha1/RunnerDeployment`, renaming it to `actions.github.com` is a breaking change and will force the user to rebuild their entire k8s cluster.
- All docker images around ARC (controller + default runner) is published to [dockerhub/summerwind](https://hub.docker.com/u/summerwind)
- The helm chart for ARC is currently hosted on [GitHub pages](https://actions-runner-controller.github.io/actions-runner-controller) for https://github.com/actions-runner-controller/actions-runner-controller, moving the repository means we will break users who install ARC via the helm chart
- The original creator's name (`summerwind`) is all over the place, including some critical parts of ARC:
- The k8s user resource API's full name is `actions.summerwind.dev/v1alpha1/RunnerDeployment`, renaming it to `actions.github.com` is a breaking change and will force the user to rebuild their entire k8s cluster.
- All docker images around ARC (controller + default runner) is published to [dockerhub/summerwind](https://hub.docker.com/u/summerwind)
- The helm chart for ARC is currently hosted on [GitHub pages](https://actions-runner-controller.github.io/actions-runner-controller) for https://github.com/actions-runner-controller/actions-runner-controller, moving the repository means we will break users who install ARC via the helm chart
# Decisions # Decisions
@@ -27,8 +29,9 @@ As part of ARC Private Beta: Repository Migration & Open Sourcing Process, we ha
- For any new resource API we are going to add, those will be named properly under GitHub, ex: `actions.github.com/v1alpha1/AutoScalingRunnerSet` - For any new resource API we are going to add, those will be named properly under GitHub, ex: `actions.github.com/v1alpha1/AutoScalingRunnerSet`
Benefits: Benefits:
- A clear separation from existing ARC: - A clear separation from existing ARC:
- Easy for the support engineer to triage income tickets and figure out whether we need to support the use case from the user - Easy for the support engineer to triage income tickets and figure out whether we need to support the use case from the user
- We won't break existing users when they upgrade to a newer version of ARC after the repository transfer - We won't break existing users when they upgrade to a newer version of ARC after the repository transfer
Based on the spike done by `@nikola-jokic`, we have confidence that we can host multiple resources with different API names under the same repository, and the published ARC controller can handle both resources properly. Based on the spike done by `@nikola-jokic`, we have confidence that we can host multiple resources with different API names under the same repository, and the published ARC controller can handle both resources properly.

View File

@@ -1,8 +1,8 @@
# ADR 0007: Adding labels to our resources # ADR 2022-12-05: Adding labels to our resources
**Date**: 2022-12-05 **Date**: 2022-12-05
**Status**: Done **Status**: Superceded [^1]
## Context ## Context
@@ -20,12 +20,15 @@ Assuming standard logging that would allow us to get all ARC logs by running
```bash ```bash
kubectl logs -l 'app.kubernetes.io/part-of=actions-runner-controller' kubectl logs -l 'app.kubernetes.io/part-of=actions-runner-controller'
``` ```
which would be very useful for development to begin with. which would be very useful for development to begin with.
The proposal is to add these sets of labels to the pods ARC creates: The proposal is to add these sets of labels to the pods ARC creates:
#### controller-manager #### controller-manager
Labels to be set by the Helm chart: Labels to be set by the Helm chart:
```yaml ```yaml
metadata: metadata:
labels: labels:
@@ -35,7 +38,9 @@ metadata:
``` ```
#### Listener #### Listener
Labels to be set by controller at creation: Labels to be set by controller at creation:
```yaml ```yaml
metadata: metadata:
labels: labels:
@@ -43,7 +48,7 @@ metadata:
app.kubernetes.io/component: runner-scale-set-listener app.kubernetes.io/component: runner-scale-set-listener
app.kubernetes.io/version: "x.x.x" app.kubernetes.io/version: "x.x.x"
actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet
# the following labels are to be extracted by the config URL # the following labels are to be extracted by the config URL
actions.github.com/enterprise: enterprise actions.github.com/enterprise: enterprise
actions.github.com/organization: organization actions.github.com/organization: organization
@@ -51,7 +56,9 @@ metadata:
``` ```
#### Runner #### Runner
Labels to be set by controller at creation: Labels to be set by controller at creation:
```yaml ```yaml
metadata: metadata:
labels: labels:
@@ -78,3 +85,5 @@ Or for example if they're having problems specifically with runners:
This way users don't have to understand ARC moving parts but we still have a This way users don't have to understand ARC moving parts but we still have a
way to target them specifically if we need to. way to target them specifically if we need to.
[^1]: Superseded by [ADR 2023-04-14](2023-04-14-adding-labels-k8s-resources.md)

View File

@@ -1,4 +1,5 @@
# ADR 0008: Pick the right runner to scale down # ADR 2022-12-27: Pick the right runner to scale down
**Date**: 2022-12-27 **Date**: 2022-12-27
**Status**: Done **Status**: Done
@@ -7,35 +8,37 @@
- A custom resource `EphemeralRunnerSet` manage a set of custom resource `EphemeralRunners` - A custom resource `EphemeralRunnerSet` manage a set of custom resource `EphemeralRunners`
- The `EphemeralRunnerSet` has `Replicas` in its `Spec`, and the responsibility of the `EphemeralRunnerSet_controller` is to reconcile a given `EphemeralRunnerSet` to have - The `EphemeralRunnerSet` has `Replicas` in its `Spec`, and the responsibility of the `EphemeralRunnerSet_controller` is to reconcile a given `EphemeralRunnerSet` to have
the same amount of `EphemeralRunners` as the `Spec.Replicas` defined. the same amount of `EphemeralRunners` as the `Spec.Replicas` defined.
- This means the `EphemeralRunnerSet_controller` will scale up the `EphemeralRunnerSet` by creating more `EphemeralRunner` in the case of the `Spec.Replicas` is higher than - This means the `EphemeralRunnerSet_controller` will scale up the `EphemeralRunnerSet` by creating more `EphemeralRunner` in the case of the `Spec.Replicas` is higher than
the current amount of `EphemeralRunners`. the current amount of `EphemeralRunners`.
- This also means the `EphemeralRunnerSet_controller` will scale down the `EphemeralRunnerSet` by finding some existing `EphemeralRunner` to delete in the case of - This also means the `EphemeralRunnerSet_controller` will scale down the `EphemeralRunnerSet` by finding some existing `EphemeralRunner` to delete in the case of
the `Spec.Replicas` is less than the current amount of `EphemeralRunners`. the `Spec.Replicas` is less than the current amount of `EphemeralRunners`.
This ADR is about how can we find the right existing `EphemeralRunner` to delete when we need to scale down. This ADR is about how can we find the right existing `EphemeralRunner` to delete when we need to scale down.
## Current approach
## Current approach
1. `EphemeralRunnerSet_controller` figure out how many `EphemeralRunner` it needs to delete, ex: need to scale down from 10 to 2 means we need to delete 8 `EphemeralRunner` 1. `EphemeralRunnerSet_controller` figure out how many `EphemeralRunner` it needs to delete, ex: need to scale down from 10 to 2 means we need to delete 8 `EphemeralRunner`
2. `EphemeralRunnerSet_controller` find all `EphemeralRunner` that is in the `Running` or `Pending` phase. 2. `EphemeralRunnerSet_controller` find all `EphemeralRunner` that is in the `Running` or `Pending` phase.
> `Pending` means the `EphemeralRunner` is still probably creating and a runner has not yet configured with the Actions service.
> `Running` means the `EphemeralRunner` is created and a runner has probably configured with Actions service, the runner may sit there idle, > `Pending` means the `EphemeralRunner` is still probably creating and a runner has not yet configured with the Actions service.
> or maybe actively running a workflow job. We don't have a clear answer for it from the ARC side. (Actions service knows it for sure) > `Running` means the `EphemeralRunner` is created and a runner has probably configured with Actions service, the runner may sit there idle,
> or maybe actively running a workflow job. We don't have a clear answer for it from the ARC side. (Actions service knows it for sure)
3. `EphemeralRunnerSet_controller` make an HTTP DELETE request to the Actions service for each `EphemeralRunner` from the previous step and ask the Actions service to delete the runner via `RunnerId`. 3. `EphemeralRunnerSet_controller` make an HTTP DELETE request to the Actions service for each `EphemeralRunner` from the previous step and ask the Actions service to delete the runner via `RunnerId`.
(The `RunnerId` is generated after the runner registered with the Actions service, and stored on the `EphemeralRunner.Status.RunnerId`) (The `RunnerId` is generated after the runner registered with the Actions service, and stored on the `EphemeralRunner.Status.RunnerId`)
> - The HTTP DELETE request looks like the following:
> `DELETE https://pipelines.actions.githubusercontent.com/WoxlUxJHrKEzIp4Nz3YmrmLlZBonrmj9xCJ1lrzcJ9ZsD1Tnw7/_apis/distributedtask/pools/0/agents/1024`
> The Actions service will return 2 types of responses:
> 1. 204 (No Content): The runner with Id 1024 has been successfully removed from the service or the runner with Id 1024 doesn't exist.
> 2. 400 (Bad Request) with JSON body that contains an error message like `JobStillRunningException`: The service can't remove this runner at this point since it has been
> assigned to a job request, the client won't be able to remove the runner until the runner finishes its current assigned job request.
4. `EphemeralRunnerSet_controller` will ignore any deletion error from runners that are still running a job, and keep trying deletion until the amount of `204` equals the amount of > - The HTTP DELETE request looks like the following:
`EphemeralRunner` needs to delete. > `DELETE https://pipelines.actions.githubusercontent.com/WoxlUxJHrKEzIp4Nz3YmrmLlZBonrmj9xCJ1lrzcJ9ZsD1Tnw7/_apis/distributedtask/pools/0/agents/1024`
> The Actions service will return 2 types of responses:
>
> 1. 204 (No Content): The runner with Id 1024 has been successfully removed from the service or the runner with Id 1024 doesn't exist.
> 2. 400 (Bad Request) with JSON body that contains an error message like `JobStillRunningException`: The service can't remove this runner at this point since it has been
> assigned to a job request, the client won't be able to remove the runner until the runner finishes its current assigned job request.
4. `EphemeralRunnerSet_controller` will ignore any deletion error from runners that are still running a job, and keep trying deletion until the amount of `204` equals the amount of
`EphemeralRunner` needs to delete.
## The problem with the current approach ## The problem with the current approach
@@ -68,6 +71,7 @@ this would be a big `NO` from a security point of view since we may not trust th
The nature of the k8s controller-runtime means we might reconcile the resource base on stale cache data. The nature of the k8s controller-runtime means we might reconcile the resource base on stale cache data.
I think our goal for the solution should be: I think our goal for the solution should be:
- Reduce wasteful HTTP requests on a scale-down as much as we can. - Reduce wasteful HTTP requests on a scale-down as much as we can.
- We can accept that we might make 1 or 2 wasteful requests to Actions service, but we can't accept making 5/10+ of them. - We can accept that we might make 1 or 2 wasteful requests to Actions service, but we can't accept making 5/10+ of them.
- See if we can meet feature parity with what the RunnerJobHook support with compromise any security concerns. - See if we can meet feature parity with what the RunnerJobHook support with compromise any security concerns.
@@ -77,9 +81,11 @@ a simple thought is how about we somehow attach some info to the `EphemeralRunne
How about we send this info from the service to the auto-scaling-listener via the existing HTTP long-poll How about we send this info from the service to the auto-scaling-listener via the existing HTTP long-poll
and let the listener patch the `EphemeralRunner.Status` to indicate it's running a job? and let the listener patch the `EphemeralRunner.Status` to indicate it's running a job?
> The listener is normally in a separate namespace with elevated permission and it's something we can trust. > The listener is normally in a separate namespace with elevated permission and it's something we can trust.
Changes: Changes:
- Introduce a new message type `JobStarted` (in addition to the existing `JobAvailable/JobAssigned/JobCompleted`) on the service side, the message is sent when a runner of the `RunnerScaleSet` get assigned to a job, - Introduce a new message type `JobStarted` (in addition to the existing `JobAvailable/JobAssigned/JobCompleted`) on the service side, the message is sent when a runner of the `RunnerScaleSet` get assigned to a job,
`RequestId`, `RunnerId`, and `RunnerName` will be included in the message. `RequestId`, `RunnerId`, and `RunnerName` will be included in the message.
- Add `RequestId (int)` to `EphemeralRunner.Status`, this will indicate which job the runner is running. - Add `RequestId (int)` to `EphemeralRunner.Status`, this will indicate which job the runner is running.

View File

@@ -1,4 +1,6 @@
# Automate updating runner version # ADR 2023-02-02: Automate updating runner version
**Date**: 2023-02-02
**Status**: Proposed **Status**: Proposed
@@ -16,6 +18,7 @@ version is updated (and this is currently done manually).
We can have another workflow running on a cadence (hourly seems sensible) and checking for new runner We can have another workflow running on a cadence (hourly seems sensible) and checking for new runner
releases, creating a PR updating `RUNNER_VERSION` in: releases, creating a PR updating `RUNNER_VERSION` in:
- `.github/workflows/release-runners.yaml` - `.github/workflows/release-runners.yaml`
- `Makefile` - `Makefile`
- `runner/Makefile` - `runner/Makefile`

View File

@@ -1,4 +1,5 @@
# ADR 0007: Limit Permissions for Service Accounts in Actions-Runner-Controller # ADR 2023-02-10: Limit Permissions for Service Accounts in Actions-Runner-Controller
**Date**: 2023-02-10 **Date**: 2023-02-10
**Status**: Pending **Status**: Pending
@@ -7,7 +8,7 @@
- `actions-runner-controller` is a Kubernetes CRD (with controller) built using https://github.com/kubernetes-sigs/controller-runtime - `actions-runner-controller` is a Kubernetes CRD (with controller) built using https://github.com/kubernetes-sigs/controller-runtime
- [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) has a default cache based k8s API client.Reader to make query k8s API server more efficiency. - [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) has a default cache based k8s API client.Reader to make query k8s API server more efficiency.
- The cache-based API client requires cluster scope `list` and `watch` permission for any resource the controller may query. - The cache-based API client requires cluster scope `list` and `watch` permission for any resource the controller may query.
@@ -22,6 +23,7 @@ There are 3 service accounts involved for a working `AutoscalingRunnerSet` based
This should have the lowest privilege (not any `RoleBinding` nor `ClusterRoleBinding`) by default, in the case of `containerMode=kubernetes`, it will get certain write permission with `RoleBinding` to limit the permission to a single namespace. This should have the lowest privilege (not any `RoleBinding` nor `ClusterRoleBinding`) by default, in the case of `containerMode=kubernetes`, it will get certain write permission with `RoleBinding` to limit the permission to a single namespace.
> References: > References:
>
> - ./charts/gha-runner-scale-set/templates/no_permission_serviceaccount.yaml > - ./charts/gha-runner-scale-set/templates/no_permission_serviceaccount.yaml
> - ./charts/gha-runner-scale-set/templates/kube_mode_role.yaml > - ./charts/gha-runner-scale-set/templates/kube_mode_role.yaml
> - ./charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml > - ./charts/gha-runner-scale-set/templates/kube_mode_role_binding.yaml
@@ -52,7 +54,7 @@ The current `ClusterRole` has the following permissions:
## Limit cluster role permission on Secrets ## Limit cluster role permission on Secrets
The cluster scope `List` `Secrets` permission might be a blocker for adopting `actions-runner-controller` for certain customers as they may have certain restriction in their cluster that simply doesn't allow any service account to have cluster scope `List Secrets` permission. The cluster scope `List` `Secrets` permission might be a blocker for adopting `actions-runner-controller` for certain customers as they may have certain restriction in their cluster that simply doesn't allow any service account to have cluster scope `List Secrets` permission.
To help these customers and improve security for `actions-runner-controller` in general, we will try to limit the `ClusterRole` permission of the controller manager's service account down to the following: To help these customers and improve security for `actions-runner-controller` in general, we will try to limit the `ClusterRole` permission of the controller manager's service account down to the following:
@@ -79,9 +81,10 @@ The `Role` and `RoleBinding` creation will happen during the `helm install demo
During `helm install demo oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller`, we will store the controller's service account info as labels on the controller `Deployment`. During `helm install demo oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller`, we will store the controller's service account info as labels on the controller `Deployment`.
Ex: Ex:
```yaml ```yaml
actions.github.com/controller-service-account-namespace: {{ .Release.Namespace }} actions.github.com/controller-service-account-namespace: {{ .Release.Namespace }}
actions.github.com/controller-service-account-name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }} actions.github.com/controller-service-account-name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}
``` ```
Introduce a new `Role` per `AutoScalingRunnerSet` installation and `RoleBinding` the `Role` with the controller's `ServiceAccount` in the namespace that each `AutoScalingRunnerSet` deployed with the following permission. Introduce a new `Role` per `AutoScalingRunnerSet` installation and `RoleBinding` the `Role` with the controller's `ServiceAccount` in the namespace that each `AutoScalingRunnerSet` deployed with the following permission.
@@ -102,8 +105,9 @@ The `gha-runner-scale-set` helm chart will use this service account to properly
The `gha-runner-scale-set` helm chart will also allow customers to explicitly provide the controller service account info, in case the `helm lookup` couldn't locate the right controller `Deployment`. The `gha-runner-scale-set` helm chart will also allow customers to explicitly provide the controller service account info, in case the `helm lookup` couldn't locate the right controller `Deployment`.
New sections in `values.yaml` of `gha-runner-scale-set`: New sections in `values.yaml` of `gha-runner-scale-set`:
```yaml ```yaml
## Optional controller service account that needs to have required Role and RoleBinding ## Optional controller service account that needs to have required Role and RoleBinding
## to operate this gha-runner-scale-set installation. ## to operate this gha-runner-scale-set installation.
## The helm chart will try to find the controller deployment and its service account at installation time. ## The helm chart will try to find the controller deployment and its service account at installation time.
## In case the helm chart can't find the right service account, you can explicitly pass in the following value ## In case the helm chart can't find the right service account, you can explicitly pass in the following value
@@ -129,5 +133,6 @@ You will deploy the `AutoScalingRunnerSet` with something like `helm install dem
In this mode, you will end up with a manager `Role` that has all Get/List/Create/Delete/Update/Patch/Watch permissions on resources we need, and a `RoleBinding` to bind the `Role` with the controller `ServiceAccount` in the watched single namespace and the controller namespace, ex: `test-namespace` and `arc-system` in the above example. In this mode, you will end up with a manager `Role` that has all Get/List/Create/Delete/Update/Patch/Watch permissions on resources we need, and a `RoleBinding` to bind the `Role` with the controller `ServiceAccount` in the watched single namespace and the controller namespace, ex: `test-namespace` and `arc-system` in the above example.
The downside of this mode: The downside of this mode:
- When you have multiple controllers deployed, they will still use the same version of the CRD. So you will need to make sure every controller you deployed has to be the same version as each other. - When you have multiple controllers deployed, they will still use the same version of the CRD. So you will need to make sure every controller you deployed has to be the same version as each other.
- You can't mismatch install both `actions-runner-controller` in this mode (watchSingleNamespace) with the regular installation mode (watchAllClusterNamespaces) in your cluster. - You can't mismatch install both `actions-runner-controller` in this mode (watchSingleNamespace) with the regular installation mode (watchAllClusterNamespaces) in your cluster.

View File

@@ -0,0 +1,89 @@
# ADR 2023-04-14: Adding labels to our resources
**Date**: 2023-04-14
**Status**: Done [^1]
## Context
Users need to provide us with logs so that we can help support and troubleshoot their issues. We need a way for our users to filter and retrieve the logs we need.
## Proposal
A good start would be a catch-all label to get all logs that are
ARC-related: one of the [recommended labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/)
is `app.kubernetes.io/part-of` and we can set that for all ARC components
to be `actions-runner-controller`.
Assuming standard logging that would allow us to get all ARC logs by running
```bash
kubectl logs -l 'app.kubernetes.io/part-of=gha-runner-scale-set-controller'
```
which would be very useful for development to begin with.
The proposal is to add these sets of labels to the pods ARC creates:
#### controller-manager
Labels to be set by the Helm chart:
```yaml
metadata:
labels:
app.kubernetes.io/part-of: gha-runner-scale-set-controller
app.kubernetes.io/component: controller-manager
app.kubernetes.io/version: "x.x.x"
```
#### Listener
Labels to be set by controller at creation:
```yaml
metadata:
labels:
app.kubernetes.io/part-of: gha-runner-scale-set-controller
app.kubernetes.io/component: runner-scale-set-listener
app.kubernetes.io/version: "x.x.x"
actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet
# the following labels are to be extracted by the config URL
actions.github.com/enterprise: enterprise
actions.github.com/organization: organization
actions.github.com/repository: repository
```
#### Runner
Labels to be set by controller at creation:
```yaml
metadata:
labels:
app.kubernetes.io/part-of: gha-runner-scale-set-controller
app.kubernetes.io/component: runner
app.kubernetes.io/version: "x.x.x"
actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet
actions.github.com/runner-name: runner-name
actions.github.com/runner-group-name: runner-group-name
# the following labels are to be extracted by the config URL
actions.github.com/enterprise: enterprise
actions.github.com/organization: organization
actions.github.com/repository: repository
```
This would allow us to ask users:
> Can you please send us the logs coming from pods labelled 'app.kubernetes.io/part-of=gha-runner-scale-set-controller'?
Or for example if they're having problems specifically with runners:
> Can you please send us the logs coming from pods labelled 'app.kubernetes.io/component=runner'?
This way users don't have to understand ARC moving parts but we still have a
way to target them specifically if we need to.
[^1]: [ADR 2022-12-05](2022-12-05-adding-labels-k8s-resources.md)

View File

@@ -6,13 +6,13 @@
## Context ## Context
*What is the issue or background knowledge necessary for future readers _What is the issue or background knowledge necessary for future readers
to understand why this ADR was written?* to understand why this ADR was written?_
## Decision ## Decision
**What** is the change being proposed? / **How** will it be implemented?* _**What** is the change being proposed? **How** will it be implemented?_
## Consequences ## Consequences
*What becomes easier or more difficult to do because of this change?* _What becomes easier or more difficult to do because of this change?_

View File

@@ -33,10 +33,14 @@ import (
//+kubebuilder:object:root=true //+kubebuilder:object:root=true
//+kubebuilder:subresource:status //+kubebuilder:subresource:status
//+kubebuilder:printcolumn:JSONPath=".spec.minRunners",name=Minimum Runners,type=number //+kubebuilder:printcolumn:JSONPath=".spec.minRunners",name=Minimum Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".spec.maxRunners",name=Maximum Runners,type=number //+kubebuilder:printcolumn:JSONPath=".spec.maxRunners",name=Maximum Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.currentRunners",name=Current Runners,type=number //+kubebuilder:printcolumn:JSONPath=".status.currentRunners",name=Current Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.state",name=State,type=string //+kubebuilder:printcolumn:JSONPath=".status.state",name=State,type=string
//+kubebuilder:printcolumn:JSONPath=".status.pendingEphemeralRunners",name=Pending Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.runningEphemeralRunners",name=Running Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.finishedEphemeralRunners",name=Finished Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.deletingEphemeralRunners",name=Deleting Runners,type=integer
// AutoscalingRunnerSet is the Schema for the autoscalingrunnersets API // AutoscalingRunnerSet is the Schema for the autoscalingrunnersets API
type AutoscalingRunnerSet struct { type AutoscalingRunnerSet struct {
@@ -228,14 +232,22 @@ type ProxyServerConfig struct {
// AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet // AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
type AutoscalingRunnerSetStatus struct { type AutoscalingRunnerSetStatus struct {
// +optional // +optional
CurrentRunners int `json:"currentRunners,omitempty"` CurrentRunners int `json:"currentRunners"`
// +optional // +optional
State string `json:"state,omitempty"` State string `json:"state"`
// EphemeralRunner counts separated by the stage ephemeral runners are in, taken from the EphemeralRunnerSet
//+optional
PendingEphemeralRunners int `json:"pendingEphemeralRunners"`
// +optional
RunningEphemeralRunners int `json:"runningEphemeralRunners"`
// +optional
FailedEphemeralRunners int `json:"failedEphemeralRunners"`
} }
func (ars *AutoscalingRunnerSet) ListenerSpecHash() string { func (ars *AutoscalingRunnerSet) ListenerSpecHash() string {
type listenerSpec = AutoscalingRunnerSetSpec
arsSpec := ars.Spec.DeepCopy() arsSpec := ars.Spec.DeepCopy()
spec := arsSpec spec := arsSpec
return hash.ComputeTemplateHash(&spec) return hash.ComputeTemplateHash(&spec)

View File

@@ -31,13 +31,27 @@ type EphemeralRunnerSetSpec struct {
// EphemeralRunnerSetStatus defines the observed state of EphemeralRunnerSet // EphemeralRunnerSetStatus defines the observed state of EphemeralRunnerSet
type EphemeralRunnerSetStatus struct { type EphemeralRunnerSetStatus struct {
// CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet. // CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet.
CurrentReplicas int `json:"currentReplicas,omitempty"` CurrentReplicas int `json:"currentReplicas"`
// EphemeralRunner counts separated by the stage ephemeral runners are in
// +optional
PendingEphemeralRunners int `json:"pendingEphemeralRunners"`
// +optional
RunningEphemeralRunners int `json:"runningEphemeralRunners"`
// +optional
FailedEphemeralRunners int `json:"failedEphemeralRunners"`
} }
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
// +kubebuilder:printcolumn:JSONPath=".spec.replicas",name="DesiredReplicas",type="integer" // +kubebuilder:printcolumn:JSONPath=".spec.replicas",name="DesiredReplicas",type="integer"
// +kubebuilder:printcolumn:JSONPath=".status.currentReplicas", name="CurrentReplicas",type="integer" // +kubebuilder:printcolumn:JSONPath=".status.currentReplicas", name="CurrentReplicas",type="integer"
//+kubebuilder:printcolumn:JSONPath=".status.pendingEphemeralRunners",name=Pending Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.runningEphemeralRunners",name=Running Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.finishedEphemeralRunners",name=Finished Runners,type=integer
//+kubebuilder:printcolumn:JSONPath=".status.deletingEphemeralRunners",name=Deleting Runners,type=integer
// EphemeralRunnerSet is the Schema for the ephemeralrunnersets API // EphemeralRunnerSet is the Schema for the ephemeralrunnersets API
type EphemeralRunnerSet struct { type EphemeralRunnerSet struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`

View File

@@ -0,0 +1,9 @@
# This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow
lint-conf: charts/.ci/lint-config.yaml
chart-repos:
- jetstack=https://charts.jetstack.io
check-version-increment: false # Disable checking that the chart version has been bumped
charts:
- charts/gha-runner-scale-set-controller
- charts/gha-runner-scale-set
skip-clean-up: true

View File

@@ -5,5 +5,3 @@ chart-repos:
check-version-increment: false # Disable checking that the chart version has been bumped check-version-increment: false # Disable checking that the chart version has been bumped
charts: charts:
- charts/actions-runner-controller - charts/actions-runner-controller
- charts/gha-runner-scale-set-controller
- charts/gha-runner-scale-set

View File

@@ -0,0 +1,5 @@
# Set the following to dummy values.
# This is only useful in CI
image:
repository: test-arc
tag: dev

View File

@@ -17,16 +17,28 @@ spec:
- additionalPrinterColumns: - additionalPrinterColumns:
- jsonPath: .spec.minRunners - jsonPath: .spec.minRunners
name: Minimum Runners name: Minimum Runners
type: number type: integer
- jsonPath: .spec.maxRunners - jsonPath: .spec.maxRunners
name: Maximum Runners name: Maximum Runners
type: number type: integer
- jsonPath: .status.currentRunners - jsonPath: .status.currentRunners
name: Current Runners name: Current Runners
type: number type: integer
- jsonPath: .status.state - jsonPath: .status.state
name: State name: State
type: string type: string
- jsonPath: .status.pendingEphemeralRunners
name: Pending Runners
type: integer
- jsonPath: .status.runningEphemeralRunners
name: Running Runners
type: integer
- jsonPath: .status.finishedEphemeralRunners
name: Finished Runners
type: integer
- jsonPath: .status.deletingEphemeralRunners
name: Deleting Runners
type: integer
name: v1alpha1 name: v1alpha1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
@@ -4306,6 +4318,12 @@ spec:
properties: properties:
currentRunners: currentRunners:
type: integer type: integer
failedEphemeralRunners:
type: integer
pendingEphemeralRunners:
type: integer
runningEphemeralRunners:
type: integer
state: state:
type: string type: string
type: object type: object

View File

@@ -21,6 +21,18 @@ spec:
- jsonPath: .status.currentReplicas - jsonPath: .status.currentReplicas
name: CurrentReplicas name: CurrentReplicas
type: integer type: integer
- jsonPath: .status.pendingEphemeralRunners
name: Pending Runners
type: integer
- jsonPath: .status.runningEphemeralRunners
name: Running Runners
type: integer
- jsonPath: .status.finishedEphemeralRunners
name: Finished Runners
type: integer
- jsonPath: .status.deletingEphemeralRunners
name: Deleting Runners
type: integer
name: v1alpha1 name: v1alpha1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
@@ -4296,6 +4308,14 @@ spec:
currentReplicas: currentReplicas:
description: CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet. description: CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet.
type: integer type: integer
failedEphemeralRunners:
type: integer
pendingEphemeralRunners:
type: integer
runningEphemeralRunners:
type: integer
required:
- currentReplicas
type: object type: object
type: object type: object
served: true served: true

View File

@@ -39,7 +39,7 @@ helm.sh/chart: {{ include "gha-runner-scale-set-controller.chart" . }}
{{- if .Chart.AppVersion }} {{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }} {{- end }}
app.kubernetes.io/part-of: {{ .Chart.Name }} app.kubernetes.io/part-of: gha-runner-scale-set-controller
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- range $k, $v := .Values.labels }} {{- range $k, $v := .Values.labels }}
{{ $k }}: {{ $v }} {{ $k }}: {{ $v }}
@@ -59,25 +59,41 @@ Create the name of the service account to use
*/}} */}}
{{- define "gha-runner-scale-set-controller.serviceAccountName" -}} {{- define "gha-runner-scale-set-controller.serviceAccountName" -}}
{{- if eq .Values.serviceAccount.name "default"}} {{- if eq .Values.serviceAccount.name "default"}}
{{- fail "serviceAccount.name cannot be set to 'default'" }} {{- fail "serviceAccount.name cannot be set to 'default'" }}
{{- end }} {{- end }}
{{- if .Values.serviceAccount.create }} {{- if .Values.serviceAccount.create }}
{{- default (include "gha-runner-scale-set-controller.fullname" .) .Values.serviceAccount.name }} {{- default (include "gha-runner-scale-set-controller.fullname" .) .Values.serviceAccount.name }}
{{- else }} {{- else }}
{{- if not .Values.serviceAccount.name }} {{- if not .Values.serviceAccount.name }}
{{- fail "serviceAccount.name must be set if serviceAccount.create is false" }} {{- fail "serviceAccount.name must be set if serviceAccount.create is false" }}
{{- else }} {{- else }}
{{- .Values.serviceAccount.name }} {{- .Values.serviceAccount.name }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- define "gha-runner-scale-set-controller.managerRoleName" -}} {{- define "gha-runner-scale-set-controller.managerClusterRoleName" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-role {{- include "gha-runner-scale-set-controller.fullname" . }}-manager-cluster-role
{{- end }} {{- end }}
{{- define "gha-runner-scale-set-controller.managerRoleBinding" -}} {{- define "gha-runner-scale-set-controller.managerClusterRoleBinding" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-rolebinding {{- include "gha-runner-scale-set-controller.fullname" . }}-manager-cluster-rolebinding
{{- end }}
{{- define "gha-runner-scale-set-controller.managerSingleNamespaceRoleName" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-single-namespace-role
{{- end }}
{{- define "gha-runner-scale-set-controller.managerSingleNamespaceRoleBinding" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-single-namespace-rolebinding
{{- end }}
{{- define "gha-runner-scale-set-controller.managerListenerRoleName" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-listener-role
{{- end }}
{{- define "gha-runner-scale-set-controller.managerListenerRoleBinding" -}}
{{- include "gha-runner-scale-set-controller.fullname" . }}-manager-listener-rolebinding
{{- end }} {{- end }}
{{- define "gha-runner-scale-set-controller.leaderElectionRoleName" -}} {{- define "gha-runner-scale-set-controller.leaderElectionRoleName" -}}
@@ -91,7 +107,7 @@ Create the name of the service account to use
{{- define "gha-runner-scale-set-controller.imagePullSecretsNames" -}} {{- define "gha-runner-scale-set-controller.imagePullSecretsNames" -}}
{{- $names := list }} {{- $names := list }}
{{- range $k, $v := . }} {{- range $k, $v := . }}
{{- $names = append $names $v.name }} {{- $names = append $names $v.name }}
{{- end }} {{- end }}
{{- $names | join ","}} {{- $names | join ","}}
{{- end }} {{- end }}

View File

@@ -5,6 +5,11 @@ metadata:
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
labels: labels:
{{- include "gha-runner-scale-set-controller.labels" . | nindent 4 }} {{- include "gha-runner-scale-set-controller.labels" . | nindent 4 }}
actions.github.com/controller-service-account-namespace: {{ .Release.Namespace }}
actions.github.com/controller-service-account-name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}
{{- if .Values.flags.watchSingleNamespace }}
actions.github.com/controller-watch-single-namespace: {{ .Values.flags.watchSingleNamespace }}
{{- end }}
spec: spec:
replicas: {{ default 1 .Values.replicaCount }} replicas: {{ default 1 .Values.replicaCount }}
selector: selector:
@@ -18,7 +23,7 @@ spec:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
labels: labels:
app.kubernetes.io/part-of: actions-runner-controller app.kubernetes.io/part-of: gha-runner-scale-set-controller
app.kubernetes.io/component: controller-manager app.kubernetes.io/component: controller-manager
app.kubernetes.io/version: {{ .Chart.Version }} app.kubernetes.io/version: {{ .Chart.Version }}
{{- include "gha-runner-scale-set-controller.selectorLabels" . | nindent 8 }} {{- include "gha-runner-scale-set-controller.selectorLabels" . | nindent 8 }}
@@ -51,13 +56,14 @@ spec:
{{- with .Values.flags.logLevel }} {{- with .Values.flags.logLevel }}
- "--log-level={{ . }}" - "--log-level={{ . }}"
{{- end }} {{- end }}
{{- with .Values.flags.watchSingleNamespace }}
- "--watch-single-namespace={{ . }}"
{{- end }}
command: command:
- "/manager" - "/manager"
env: env:
- name: CONTROLLER_MANAGER_POD_NAME - name: CONTROLLER_MANAGER_CONTAINER_IMAGE
valueFrom: value: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
fieldRef:
fieldPath: metadata.name
- name: CONTROLLER_MANAGER_POD_NAMESPACE - name: CONTROLLER_MANAGER_POD_NAMESPACE
valueFrom: valueFrom:
fieldRef: fieldRef:
@@ -98,4 +104,4 @@ spec:
{{- with .Values.tolerations }} {{- with .Values.tolerations }}
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}

View File

@@ -1,4 +1,4 @@
{{- if gt (int (default 1 .Values.replicaCount)) 1 -}} {{- if gt (int (default 1 .Values.replicaCount)) 1 }}
# permissions to do leader election. # permissions to do leader election.
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: Role kind: Role

View File

@@ -1,4 +1,4 @@
{{- if gt (int (default 1 .Values.replicaCount)) 1 -}} {{- if gt (int (default 1 .Values.replicaCount)) 1 }}
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding kind: RoleBinding
metadata: metadata:

View File

@@ -1,7 +1,8 @@
{{- if empty .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: {{ include "gha-runner-scale-set-controller.managerRoleName" . }} name: {{ include "gha-runner-scale-set-controller.managerClusterRoleName" . }}
rules: rules:
- apiGroups: - apiGroups:
- actions.github.com - actions.github.com
@@ -20,6 +21,7 @@ rules:
resources: resources:
- autoscalingrunnersets/finalizers - autoscalingrunnersets/finalizers
verbs: verbs:
- patch
- update - update
- apiGroups: - apiGroups:
- actions.github.com - actions.github.com
@@ -54,6 +56,7 @@ rules:
resources: resources:
- autoscalinglisteners/finalizers - autoscalinglisteners/finalizers
verbs: verbs:
- patch
- update - update
- apiGroups: - apiGroups:
- actions.github.com - actions.github.com
@@ -92,13 +95,8 @@ rules:
resources: resources:
- ephemeralrunners/finalizers - ephemeralrunners/finalizers
verbs: verbs:
- create
- delete
- get
- list
- patch - patch
- update - update
- watch
- apiGroups: - apiGroups:
- actions.github.com - actions.github.com
resources: resources:
@@ -112,45 +110,13 @@ rules:
resources: resources:
- pods - pods
verbs: verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- pods/status
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list - list
- watch - watch
- update
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
- serviceaccounts - serviceaccounts
verbs: verbs:
- create
- delete
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- list - list
- watch - watch
- apiGroups: - apiGroups:
@@ -158,10 +124,6 @@ rules:
resources: resources:
- rolebindings - rolebindings
verbs: verbs:
- create
- delete
- get
- update
- list - list
- watch - watch
- apiGroups: - apiGroups:
@@ -169,9 +131,6 @@ rules:
resources: resources:
- roles - roles
verbs: verbs:
- create
- delete
- get
- update
- list - list
- watch - watch
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if empty .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "gha-runner-scale-set-controller.managerClusterRoleBinding" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "gha-runner-scale-set-controller.managerClusterRoleName" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,40 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-runner-scale-set-controller.managerListenerRoleName" . }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- create
- delete
- get
- apiGroups:
- ""
resources:
- pods/status
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- patch
- update
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- create
- delete
- get
- patch
- update

View File

@@ -1,11 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: RoleBinding
metadata: metadata:
name: {{ include "gha-runner-scale-set-controller.managerRoleBinding" . }} name: {{ include "gha-runner-scale-set-controller.managerListenerRoleBinding" . }}
namespace: {{ .Release.Namespace }}
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: ClusterRole kind: Role
name: {{ include "gha-runner-scale-set-controller.managerRoleName" . }} name: {{ include "gha-runner-scale-set-controller.managerListenerRoleName" . }}
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }} name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}

View File

@@ -0,0 +1,84 @@
{{- if .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleName" . }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- actions.github.com
resources:
- autoscalinglisteners
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- actions.github.com
resources:
- autoscalinglisteners/status
verbs:
- get
- patch
- update
- apiGroups:
- actions.github.com
resources:
- autoscalinglisteners/finalizers
verbs:
- patch
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- list
- watch
- apiGroups:
- actions.github.com
resources:
- autoscalingrunnersets
verbs:
- list
- watch
- apiGroups:
- actions.github.com
resources:
- ephemeralrunnersets
verbs:
- list
- watch
- apiGroups:
- actions.github.com
resources:
- ephemeralrunners
verbs:
- list
- watch
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleBinding" . }}
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleName" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -0,0 +1,117 @@
{{- if .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleName" . }}
namespace: {{ .Values.flags.watchSingleNamespace }}
rules:
- apiGroups:
- actions.github.com
resources:
- autoscalingrunnersets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- actions.github.com
resources:
- autoscalingrunnersets/finalizers
verbs:
- patch
- update
- apiGroups:
- actions.github.com
resources:
- autoscalingrunnersets/status
verbs:
- get
- patch
- update
- apiGroups:
- actions.github.com
resources:
- ephemeralrunnersets
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- actions.github.com
resources:
- ephemeralrunnersets/status
verbs:
- get
- patch
- update
- apiGroups:
- actions.github.com
resources:
- ephemeralrunners
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- actions.github.com
resources:
- ephemeralrunners/finalizers
verbs:
- patch
- update
- apiGroups:
- actions.github.com
resources:
- ephemeralrunners/status
verbs:
- get
- patch
- update
- apiGroups:
- actions.github.com
resources:
- autoscalinglisteners
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- list
- watch
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- list
- watch
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if .Values.flags.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleBinding" . }}
namespace: {{ .Values.flags.watchSingleNamespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-runner-scale-set-controller.managerSingleNamespaceRoleName" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-runner-scale-set-controller.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@@ -1,4 +1,4 @@
{{- if .Values.serviceAccount.create -}} {{- if .Values.serviceAccount.create }}
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:

View File

@@ -147,7 +147,7 @@ func TestTemplate_NotCreateServiceAccount_ServiceAccountNotSet(t *testing.T) {
assert.ErrorContains(t, err, "serviceAccount.name must be set if serviceAccount.create is false", "We should get an error because the default service account cannot be used") assert.ErrorContains(t, err, "serviceAccount.name must be set if serviceAccount.create is false", "We should get an error because the default service account cannot be used")
} }
func TestTemplate_CreateManagerRole(t *testing.T) { func TestTemplate_CreateManagerClusterRole(t *testing.T) {
t.Parallel() t.Parallel()
// Path to the helm chart we will test // Path to the helm chart we will test
@@ -162,17 +162,23 @@ func TestTemplate_CreateManagerRole(t *testing.T) {
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_role.yaml"}) output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_cluster_role.yaml"})
var managerRole rbacv1.ClusterRole var managerClusterRole rbacv1.ClusterRole
helm.UnmarshalK8SYaml(t, output, &managerRole) helm.UnmarshalK8SYaml(t, output, &managerClusterRole)
assert.Empty(t, managerRole.Namespace, "ClusterRole should not have a namespace") assert.Empty(t, managerClusterRole.Namespace, "ClusterRole should not have a namespace")
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-role", managerRole.Name) assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-cluster-role", managerClusterRole.Name)
assert.Equal(t, 18, len(managerRole.Rules)) assert.Equal(t, 15, len(managerClusterRole.Rules))
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_controller_role.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_single_namespace_controller_role.yaml in chart", "We should get an error because the template should be skipped")
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_watch_role.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_single_namespace_watch_role.yaml in chart", "We should get an error because the template should be skipped")
} }
func TestTemplate_ManagerRoleBinding(t *testing.T) { func TestTemplate_ManagerClusterRoleBinding(t *testing.T) {
t.Parallel() t.Parallel()
// Path to the helm chart we will test // Path to the helm chart we will test
@@ -189,16 +195,80 @@ func TestTemplate_ManagerRoleBinding(t *testing.T) {
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_role_binding.yaml"}) output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_cluster_role_binding.yaml"})
var managerRoleBinding rbacv1.ClusterRoleBinding var managerClusterRoleBinding rbacv1.ClusterRoleBinding
helm.UnmarshalK8SYaml(t, output, &managerRoleBinding) helm.UnmarshalK8SYaml(t, output, &managerClusterRoleBinding)
assert.Empty(t, managerRoleBinding.Namespace, "ClusterRoleBinding should not have a namespace") assert.Empty(t, managerClusterRoleBinding.Namespace, "ClusterRoleBinding should not have a namespace")
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-rolebinding", managerRoleBinding.Name) assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-cluster-rolebinding", managerClusterRoleBinding.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-role", managerRoleBinding.RoleRef.Name) assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-cluster-role", managerClusterRoleBinding.RoleRef.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", managerRoleBinding.Subjects[0].Name) assert.Equal(t, "test-arc-gha-runner-scale-set-controller", managerClusterRoleBinding.Subjects[0].Name)
assert.Equal(t, namespaceName, managerRoleBinding.Subjects[0].Namespace) assert.Equal(t, namespaceName, managerClusterRoleBinding.Subjects[0].Namespace)
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_controller_role_binding.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_single_namespace_controller_role_binding.yaml in chart", "We should get an error because the template should be skipped")
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_watch_role_binding.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_single_namespace_watch_role_binding.yaml in chart", "We should get an error because the template should be skipped")
}
func TestTemplate_CreateManagerListenerRole(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_listener_role.yaml"})
var managerListenerRole rbacv1.Role
helm.UnmarshalK8SYaml(t, output, &managerListenerRole)
assert.Equal(t, namespaceName, managerListenerRole.Namespace, "Role should have a namespace")
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-listener-role", managerListenerRole.Name)
assert.Equal(t, 4, len(managerListenerRole.Rules))
assert.Equal(t, "pods", managerListenerRole.Rules[0].Resources[0])
assert.Equal(t, "pods/status", managerListenerRole.Rules[1].Resources[0])
assert.Equal(t, "secrets", managerListenerRole.Rules[2].Resources[0])
assert.Equal(t, "serviceaccounts", managerListenerRole.Rules[3].Resources[0])
}
func TestTemplate_ManagerListenerRoleBinding(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"serviceAccount.create": "true",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_listener_role_binding.yaml"})
var managerListenerRoleBinding rbacv1.RoleBinding
helm.UnmarshalK8SYaml(t, output, &managerListenerRoleBinding)
assert.Equal(t, namespaceName, managerListenerRoleBinding.Namespace, "RoleBinding should have a namespace")
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-listener-rolebinding", managerListenerRoleBinding.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-listener-role", managerListenerRoleBinding.RoleRef.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", managerListenerRoleBinding.Subjects[0].Name)
assert.Equal(t, namespaceName, managerListenerRoleBinding.Subjects[0].Namespace)
} }
func TestTemplate_ControllerDeployment_Defaults(t *testing.T) { func TestTemplate_ControllerDeployment_Defaults(t *testing.T) {
@@ -237,6 +307,10 @@ func TestTemplate_ControllerDeployment_Defaults(t *testing.T) {
assert.Equal(t, "test-arc", deployment.Labels["app.kubernetes.io/instance"]) assert.Equal(t, "test-arc", deployment.Labels["app.kubernetes.io/instance"])
assert.Equal(t, chart.AppVersion, deployment.Labels["app.kubernetes.io/version"]) assert.Equal(t, chart.AppVersion, deployment.Labels["app.kubernetes.io/version"])
assert.Equal(t, "Helm", deployment.Labels["app.kubernetes.io/managed-by"]) assert.Equal(t, "Helm", deployment.Labels["app.kubernetes.io/managed-by"])
assert.Equal(t, namespaceName, deployment.Labels["actions.github.com/controller-service-account-namespace"])
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", deployment.Labels["actions.github.com/controller-service-account-name"])
assert.NotContains(t, deployment.Labels, "actions.github.com/controller-watch-single-namespace")
assert.Equal(t, "gha-runner-scale-set-controller", deployment.Labels["app.kubernetes.io/part-of"])
assert.Equal(t, int32(1), *deployment.Spec.Replicas) assert.Equal(t, int32(1), *deployment.Spec.Replicas)
@@ -261,9 +335,11 @@ func TestTemplate_ControllerDeployment_Defaults(t *testing.T) {
assert.Nil(t, deployment.Spec.Template.Spec.Affinity) assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0) assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) assert.Len(t, deployment.Spec.Template.Spec.Containers, 1)
assert.Equal(t, "manager", deployment.Spec.Template.Spec.Containers[0].Name) assert.Equal(t, "manager", deployment.Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, "ghcr.io/actions/gha-runner-scale-set-controller:dev", deployment.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, managerImage, deployment.Spec.Template.Spec.Containers[0].Image)
assert.Equal(t, corev1.PullIfNotPresent, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy) assert.Equal(t, corev1.PullIfNotPresent, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy)
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Command, 1) assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Command, 1)
@@ -274,8 +350,8 @@ func TestTemplate_ControllerDeployment_Defaults(t *testing.T) {
assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[1]) assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[1])
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Env, 2) assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Env, 2)
assert.Equal(t, "CONTROLLER_MANAGER_POD_NAME", deployment.Spec.Template.Spec.Containers[0].Env[0].Name) assert.Equal(t, "CONTROLLER_MANAGER_CONTAINER_IMAGE", deployment.Spec.Template.Spec.Containers[0].Env[0].Name)
assert.Equal(t, "metadata.name", deployment.Spec.Template.Spec.Containers[0].Env[0].ValueFrom.FieldRef.FieldPath) assert.Equal(t, managerImage, deployment.Spec.Template.Spec.Containers[0].Env[0].Value)
assert.Equal(t, "CONTROLLER_MANAGER_POD_NAMESPACE", deployment.Spec.Template.Spec.Containers[0].Env[1].Name) assert.Equal(t, "CONTROLLER_MANAGER_POD_NAMESPACE", deployment.Spec.Template.Spec.Containers[0].Env[1].Name)
assert.Equal(t, "metadata.namespace", deployment.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath) assert.Equal(t, "metadata.namespace", deployment.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath)
@@ -341,6 +417,7 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
assert.Equal(t, "test-arc", deployment.Labels["app.kubernetes.io/instance"]) assert.Equal(t, "test-arc", deployment.Labels["app.kubernetes.io/instance"])
assert.Equal(t, chart.AppVersion, deployment.Labels["app.kubernetes.io/version"]) assert.Equal(t, chart.AppVersion, deployment.Labels["app.kubernetes.io/version"])
assert.Equal(t, "Helm", deployment.Labels["app.kubernetes.io/managed-by"]) assert.Equal(t, "Helm", deployment.Labels["app.kubernetes.io/managed-by"])
assert.Equal(t, "gha-runner-scale-set-controller", deployment.Labels["app.kubernetes.io/part-of"])
assert.Equal(t, "bar", deployment.Labels["foo"]) assert.Equal(t, "bar", deployment.Labels["foo"])
assert.Equal(t, "actions", deployment.Labels["github"]) assert.Equal(t, "actions", deployment.Labels["github"])
@@ -375,9 +452,11 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 1) assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 1)
assert.Equal(t, "foo", deployment.Spec.Template.Spec.Tolerations[0].Key) assert.Equal(t, "foo", deployment.Spec.Template.Spec.Tolerations[0].Key)
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
assert.Len(t, deployment.Spec.Template.Spec.Containers, 1) assert.Len(t, deployment.Spec.Template.Spec.Containers, 1)
assert.Equal(t, "manager", deployment.Spec.Template.Spec.Containers[0].Name) assert.Equal(t, "manager", deployment.Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, "ghcr.io/actions/gha-runner-scale-set-controller:dev", deployment.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, managerImage, deployment.Spec.Template.Spec.Containers[0].Image)
assert.Equal(t, corev1.PullAlways, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy) assert.Equal(t, corev1.PullAlways, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy)
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Command, 1) assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Command, 1)
@@ -389,8 +468,8 @@ func TestTemplate_ControllerDeployment_Customize(t *testing.T) {
assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[2]) assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[2])
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Env, 2) assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Env, 2)
assert.Equal(t, "CONTROLLER_MANAGER_POD_NAME", deployment.Spec.Template.Spec.Containers[0].Env[0].Name) assert.Equal(t, "CONTROLLER_MANAGER_CONTAINER_IMAGE", deployment.Spec.Template.Spec.Containers[0].Env[0].Name)
assert.Equal(t, "metadata.name", deployment.Spec.Template.Spec.Containers[0].Env[0].ValueFrom.FieldRef.FieldPath) assert.Equal(t, managerImage, deployment.Spec.Template.Spec.Containers[0].Env[0].Value)
assert.Equal(t, "CONTROLLER_MANAGER_POD_NAMESPACE", deployment.Spec.Template.Spec.Containers[0].Env[1].Name) assert.Equal(t, "CONTROLLER_MANAGER_POD_NAMESPACE", deployment.Spec.Template.Spec.Containers[0].Env[1].Name)
assert.Equal(t, "metadata.namespace", deployment.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath) assert.Equal(t, "metadata.namespace", deployment.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath)
@@ -531,3 +610,215 @@ func TestTemplate_ControllerDeployment_ForwardImagePullSecrets(t *testing.T) {
assert.Equal(t, "--auto-scaler-image-pull-secrets=dockerhub,ghcr", deployment.Spec.Template.Spec.Containers[0].Args[1]) assert.Equal(t, "--auto-scaler-image-pull-secrets=dockerhub,ghcr", deployment.Spec.Template.Spec.Containers[0].Args[1])
assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[2]) assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[2])
} }
func TestTemplate_ControllerDeployment_WatchSingleNamespace(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
chartContent, err := os.ReadFile(filepath.Join(helmChartPath, "Chart.yaml"))
require.NoError(t, err)
chart := new(Chart)
err = yaml.Unmarshal(chartContent, chart)
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"image.tag": "dev",
"flags.watchSingleNamespace": "demo",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/deployment.yaml"})
var deployment appsv1.Deployment
helm.UnmarshalK8SYaml(t, output, &deployment)
assert.Equal(t, namespaceName, deployment.Namespace)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", deployment.Name)
assert.Equal(t, "gha-runner-scale-set-controller-"+chart.Version, deployment.Labels["helm.sh/chart"])
assert.Equal(t, "gha-runner-scale-set-controller", deployment.Labels["app.kubernetes.io/name"])
assert.Equal(t, "test-arc", deployment.Labels["app.kubernetes.io/instance"])
assert.Equal(t, chart.AppVersion, deployment.Labels["app.kubernetes.io/version"])
assert.Equal(t, "Helm", deployment.Labels["app.kubernetes.io/managed-by"])
assert.Equal(t, namespaceName, deployment.Labels["actions.github.com/controller-service-account-namespace"])
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", deployment.Labels["actions.github.com/controller-service-account-name"])
assert.Equal(t, "demo", deployment.Labels["actions.github.com/controller-watch-single-namespace"])
assert.Equal(t, int32(1), *deployment.Spec.Replicas)
assert.Equal(t, "gha-runner-scale-set-controller", deployment.Spec.Selector.MatchLabels["app.kubernetes.io/name"])
assert.Equal(t, "test-arc", deployment.Spec.Selector.MatchLabels["app.kubernetes.io/instance"])
assert.Equal(t, "gha-runner-scale-set-controller", deployment.Spec.Template.Labels["app.kubernetes.io/name"])
assert.Equal(t, "test-arc", deployment.Spec.Template.Labels["app.kubernetes.io/instance"])
assert.Equal(t, "manager", deployment.Spec.Template.Annotations["kubectl.kubernetes.io/default-container"])
assert.Len(t, deployment.Spec.Template.Spec.ImagePullSecrets, 0)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", deployment.Spec.Template.Spec.ServiceAccountName)
assert.Nil(t, deployment.Spec.Template.Spec.SecurityContext)
assert.Empty(t, deployment.Spec.Template.Spec.PriorityClassName)
assert.Equal(t, int64(10), *deployment.Spec.Template.Spec.TerminationGracePeriodSeconds)
assert.Len(t, deployment.Spec.Template.Spec.Volumes, 1)
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Volumes[0].Name)
assert.NotNil(t, 10, deployment.Spec.Template.Spec.Volumes[0].EmptyDir)
assert.Len(t, deployment.Spec.Template.Spec.NodeSelector, 0)
assert.Nil(t, deployment.Spec.Template.Spec.Affinity)
assert.Len(t, deployment.Spec.Template.Spec.Tolerations, 0)
managerImage := "ghcr.io/actions/gha-runner-scale-set-controller:dev"
assert.Len(t, deployment.Spec.Template.Spec.Containers, 1)
assert.Equal(t, "manager", deployment.Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, "ghcr.io/actions/gha-runner-scale-set-controller:dev", deployment.Spec.Template.Spec.Containers[0].Image)
assert.Equal(t, corev1.PullIfNotPresent, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy)
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Command, 1)
assert.Equal(t, "/manager", deployment.Spec.Template.Spec.Containers[0].Command[0])
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Args, 3)
assert.Equal(t, "--auto-scaling-runner-set-only", deployment.Spec.Template.Spec.Containers[0].Args[0])
assert.Equal(t, "--log-level=debug", deployment.Spec.Template.Spec.Containers[0].Args[1])
assert.Equal(t, "--watch-single-namespace=demo", deployment.Spec.Template.Spec.Containers[0].Args[2])
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].Env, 2)
assert.Equal(t, "CONTROLLER_MANAGER_CONTAINER_IMAGE", deployment.Spec.Template.Spec.Containers[0].Env[0].Name)
assert.Equal(t, managerImage, deployment.Spec.Template.Spec.Containers[0].Env[0].Value)
assert.Equal(t, "CONTROLLER_MANAGER_POD_NAMESPACE", deployment.Spec.Template.Spec.Containers[0].Env[1].Name)
assert.Equal(t, "metadata.namespace", deployment.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath)
assert.Empty(t, deployment.Spec.Template.Spec.Containers[0].Resources)
assert.Nil(t, deployment.Spec.Template.Spec.Containers[0].SecurityContext)
assert.Len(t, deployment.Spec.Template.Spec.Containers[0].VolumeMounts, 1)
assert.Equal(t, "tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name)
assert.Equal(t, "/tmp", deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath)
}
func TestTemplate_WatchSingleNamespace_NotCreateManagerClusterRole(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"flags.watchSingleNamespace": "demo",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_cluster_role.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_cluster_role.yaml in chart", "We should get an error because the template should be skipped")
}
func TestTemplate_WatchSingleNamespace_NotManagerClusterRoleBinding(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"serviceAccount.create": "true",
"flags.watchSingleNamespace": "demo",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
_, err = helm.RenderTemplateE(t, options, helmChartPath, releaseName, []string{"templates/manager_cluster_role_binding.yaml"})
assert.ErrorContains(t, err, "could not find template templates/manager_cluster_role_binding.yaml in chart", "We should get an error because the template should be skipped")
}
func TestTemplate_CreateManagerSingleNamespaceRole(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"flags.watchSingleNamespace": "demo",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_controller_role.yaml"})
var managerSingleNamespaceControllerRole rbacv1.Role
helm.UnmarshalK8SYaml(t, output, &managerSingleNamespaceControllerRole)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-role", managerSingleNamespaceControllerRole.Name)
assert.Equal(t, namespaceName, managerSingleNamespaceControllerRole.Namespace)
assert.Equal(t, 10, len(managerSingleNamespaceControllerRole.Rules))
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_watch_role.yaml"})
var managerSingleNamespaceWatchRole rbacv1.Role
helm.UnmarshalK8SYaml(t, output, &managerSingleNamespaceWatchRole)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-role", managerSingleNamespaceWatchRole.Name)
assert.Equal(t, "demo", managerSingleNamespaceWatchRole.Namespace)
assert.Equal(t, 13, len(managerSingleNamespaceWatchRole.Rules))
}
func TestTemplate_ManagerSingleNamespaceRoleBinding(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set-controller")
require.NoError(t, err)
releaseName := "test-arc"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"flags.watchSingleNamespace": "demo",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_controller_role_binding.yaml"})
var managerSingleNamespaceControllerRoleBinding rbacv1.RoleBinding
helm.UnmarshalK8SYaml(t, output, &managerSingleNamespaceControllerRoleBinding)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-rolebinding", managerSingleNamespaceControllerRoleBinding.Name)
assert.Equal(t, namespaceName, managerSingleNamespaceControllerRoleBinding.Namespace)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-role", managerSingleNamespaceControllerRoleBinding.RoleRef.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", managerSingleNamespaceControllerRoleBinding.Subjects[0].Name)
assert.Equal(t, namespaceName, managerSingleNamespaceControllerRoleBinding.Subjects[0].Namespace)
output = helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_single_namespace_watch_role_binding.yaml"})
var managerSingleNamespaceWatchRoleBinding rbacv1.RoleBinding
helm.UnmarshalK8SYaml(t, output, &managerSingleNamespaceWatchRoleBinding)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-rolebinding", managerSingleNamespaceWatchRoleBinding.Name)
assert.Equal(t, "demo", managerSingleNamespaceWatchRoleBinding.Namespace)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-single-namespace-role", managerSingleNamespaceWatchRoleBinding.RoleRef.Name)
assert.Equal(t, "test-arc-gha-runner-scale-set-controller", managerSingleNamespaceWatchRoleBinding.Subjects[0].Name)
assert.Equal(t, namespaceName, managerSingleNamespaceWatchRoleBinding.Subjects[0].Namespace)
}

View File

@@ -68,3 +68,7 @@ flags:
# Log level can be set here with one of the following values: "debug", "info", "warn", "error". # Log level can be set here with one of the following values: "debug", "info", "warn", "error".
# Defaults to "debug". # Defaults to "debug".
logLevel: "debug" logLevel: "debug"
# Restricts the controller to only watch resources in the desired namespace.
# Defaults to watch all namespaces when unset.
# watchSingleNamespace: ""

View File

@@ -3,4 +3,4 @@
githubConfigUrl: https://github.com/actions/actions-runner-controller githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret: githubConfigSecret:
github_token: test github_token: test

View File

@@ -75,19 +75,15 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} {{- end }}
{{- define "gha-runner-scale-set.dind-init-container" -}} {{- define "gha-runner-scale-set.dind-init-container" -}}
{{- range $i, $val := .Values.template.spec.containers -}} {{- range $i, $val := .Values.template.spec.containers }}
{{- if eq $val.name "runner" -}} {{- if eq $val.name "runner" }}
image: {{ $val.image }} image: {{ $val.image }}
{{- if $val.imagePullSecrets }}
imagePullSecrets:
{{ $val.imagePullSecrets | toYaml -}}
{{- end }}
command: ["cp"] command: ["cp"]
args: ["-r", "-v", "/home/runner/externals/.", "/home/runner/tmpDir/"] args: ["-r", "-v", "/home/runner/externals/.", "/home/runner/tmpDir/"]
volumeMounts: volumeMounts:
- name: dind-externals - name: dind-externals
mountPath: /home/runner/tmpDir mountPath: /home/runner/tmpDir
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
@@ -124,7 +120,7 @@ volumeMounts:
{{- $createWorkVolume := 1 }} {{- $createWorkVolume := 1 }}
{{- range $i, $volume := .Values.template.spec.volumes }} {{- range $i, $volume := .Values.template.spec.volumes }}
{{- if eq $volume.name "work" }} {{- if eq $volume.name "work" }}
{{- $createWorkVolume = 0 -}} {{- $createWorkVolume = 0 }}
- {{ $volume | toYaml | nindent 2 }} - {{ $volume | toYaml | nindent 2 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
@@ -138,7 +134,7 @@ volumeMounts:
{{- $createWorkVolume := 1 }} {{- $createWorkVolume := 1 }}
{{- range $i, $volume := .Values.template.spec.volumes }} {{- range $i, $volume := .Values.template.spec.volumes }}
{{- if eq $volume.name "work" }} {{- if eq $volume.name "work" }}
{{- $createWorkVolume = 0 -}} {{- $createWorkVolume = 0 }}
- {{ $volume | toYaml | nindent 2 }} - {{ $volume | toYaml | nindent 2 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
@@ -160,25 +156,28 @@ volumeMounts:
{{- end }} {{- end }}
{{- define "gha-runner-scale-set.non-runner-containers" -}} {{- define "gha-runner-scale-set.non-runner-containers" -}}
{{- range $i, $container := .Values.template.spec.containers -}} {{- range $i, $container := .Values.template.spec.containers }}
{{- if ne $container.name "runner" -}} {{- if ne $container.name "runner" }}
- name: {{ $container.name }} - {{ $container | toYaml | nindent 2 }}
{{- range $key, $val := $container }} {{- end }}
{{- if ne $key "name" }} {{- end }}
{{ $key }}: {{ $val }} {{- end }}
{{- end }}
{{- end }} {{- define "gha-runner-scale-set.non-runner-non-dind-containers" -}}
{{- range $i, $container := .Values.template.spec.containers }}
{{- if and (ne $container.name "runner") (ne $container.name "dind") }}
- {{ $container | toYaml | nindent 2 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- define "gha-runner-scale-set.dind-runner-container" -}} {{- define "gha-runner-scale-set.dind-runner-container" -}}
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} {{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
{{- range $i, $container := .Values.template.spec.containers -}} {{- range $i, $container := .Values.template.spec.containers }}
{{- if eq $container.name "runner" -}} {{- if eq $container.name "runner" }}
{{- range $key, $val := $container }} {{- range $key, $val := $container }}
{{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }} {{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }}
{{ $key }}: {{ $val }} {{ $key }}: {{ $val | toYaml | nindent 2 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- $setDockerHost := 1 }} {{- $setDockerHost := 1 }}
@@ -195,29 +194,24 @@ env:
{{- with $container.env }} {{- with $container.env }}
{{- range $i, $env := . }} {{- range $i, $env := . }}
{{- if eq $env.name "DOCKER_HOST" }} {{- if eq $env.name "DOCKER_HOST" }}
{{- $setDockerHost = 0 -}} {{- $setDockerHost = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "DOCKER_TLS_VERIFY" }} {{- if eq $env.name "DOCKER_TLS_VERIFY" }}
{{- $setDockerTlsVerify = 0 -}} {{- $setDockerTlsVerify = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "DOCKER_CERT_PATH" }} {{- if eq $env.name "DOCKER_CERT_PATH" }}
{{- $setDockerCertPath = 0 -}} {{- $setDockerCertPath = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" }} {{- if eq $env.name "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" }}
{{- $setRunnerWaitDocker = 0 -}} {{- $setRunnerWaitDocker = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }} {{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
{{- $setNodeExtraCaCerts = 0 -}} {{- $setNodeExtraCaCerts = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }} {{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
{{- $setRunnerUpdateCaCerts = 0 -}} {{- $setRunnerUpdateCaCerts = 0 }}
{{- end }}
- name: {{ $env.name }}
{{- range $envKey, $envVal := $env }}
{{- if ne $envKey "name" }}
{{ $envKey }}: {{ $envVal | toYaml | nindent 8 }}
{{- end }}
{{- end }} {{- end }}
- {{ $env | toYaml | nindent 4 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $setDockerHost }} {{- if $setDockerHost }}
@@ -254,20 +248,15 @@ volumeMounts:
{{- with $container.volumeMounts }} {{- with $container.volumeMounts }}
{{- range $i, $volMount := . }} {{- range $i, $volMount := . }}
{{- if eq $volMount.name "work" }} {{- if eq $volMount.name "work" }}
{{- $mountWork = 0 -}} {{- $mountWork = 0 }}
{{- end }} {{- end }}
{{- if eq $volMount.name "dind-cert" }} {{- if eq $volMount.name "dind-cert" }}
{{- $mountDindCert = 0 -}} {{- $mountDindCert = 0 }}
{{- end }} {{- end }}
{{- if eq $volMount.name "github-server-tls-cert" }} {{- if eq $volMount.name "github-server-tls-cert" }}
{{- $mountGitHubServerTLS = 0 -}} {{- $mountGitHubServerTLS = 0 }}
{{- end }}
- name: {{ $volMount.name }}
{{- range $mountKey, $mountVal := $volMount }}
{{- if ne $mountKey "name" }}
{{ $mountKey }}: {{ $mountVal | toYaml | nindent 8 }}
{{- end }}
{{- end }} {{- end }}
- {{ $volMount | toYaml | nindent 4 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $mountWork }} {{- if $mountWork }}
@@ -290,11 +279,11 @@ volumeMounts:
{{- define "gha-runner-scale-set.kubernetes-mode-runner-container" -}} {{- define "gha-runner-scale-set.kubernetes-mode-runner-container" -}}
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} {{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
{{- range $i, $container := .Values.template.spec.containers -}} {{- range $i, $container := .Values.template.spec.containers }}
{{- if eq $container.name "runner" -}} {{- if eq $container.name "runner" }}
{{- range $key, $val := $container }} {{- range $key, $val := $container }}
{{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }} {{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }}
{{ $key }}: {{ $val }} {{ $key }}: {{ $val | toYaml | nindent 2 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- $setContainerHooks := 1 }} {{- $setContainerHooks := 1 }}
@@ -310,26 +299,21 @@ env:
{{- with $container.env }} {{- with $container.env }}
{{- range $i, $env := . }} {{- range $i, $env := . }}
{{- if eq $env.name "ACTIONS_RUNNER_CONTAINER_HOOKS" }} {{- if eq $env.name "ACTIONS_RUNNER_CONTAINER_HOOKS" }}
{{- $setContainerHooks = 0 -}} {{- $setContainerHooks = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "ACTIONS_RUNNER_POD_NAME" }} {{- if eq $env.name "ACTIONS_RUNNER_POD_NAME" }}
{{- $setPodName = 0 -}} {{- $setPodName = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER" }} {{- if eq $env.name "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER" }}
{{- $setRequireJobContainer = 0 -}} {{- $setRequireJobContainer = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }} {{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
{{- $setNodeExtraCaCerts = 0 -}} {{- $setNodeExtraCaCerts = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }} {{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
{{- $setRunnerUpdateCaCerts = 0 -}} {{- $setRunnerUpdateCaCerts = 0 }}
{{- end }}
- name: {{ $env.name }}
{{- range $envKey, $envVal := $env }}
{{- if ne $envKey "name" }}
{{ $envKey }}: {{ $envVal | toYaml | nindent 8 }}
{{- end }}
{{- end }} {{- end }}
- {{ $env | toYaml | nindent 4 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $setContainerHooks }} {{- if $setContainerHooks }}
@@ -363,17 +347,12 @@ volumeMounts:
{{- with $container.volumeMounts }} {{- with $container.volumeMounts }}
{{- range $i, $volMount := . }} {{- range $i, $volMount := . }}
{{- if eq $volMount.name "work" }} {{- if eq $volMount.name "work" }}
{{- $mountWork = 0 -}} {{- $mountWork = 0 }}
{{- end }} {{- end }}
{{- if eq $volMount.name "github-server-tls-cert" }} {{- if eq $volMount.name "github-server-tls-cert" }}
{{- $mountGitHubServerTLS = 0 -}} {{- $mountGitHubServerTLS = 0 }}
{{- end }}
- name: {{ $volMount.name }}
{{- range $mountKey, $mountVal := $volMount }}
{{- if ne $mountKey "name" }}
{{ $mountKey }}: {{ $mountVal | toYaml | nindent 8 }}
{{- end }}
{{- end }} {{- end }}
- {{ $volMount | toYaml | nindent 4 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $mountWork }} {{- if $mountWork }}
@@ -391,14 +370,14 @@ volumeMounts:
{{- define "gha-runner-scale-set.default-mode-runner-containers" -}} {{- define "gha-runner-scale-set.default-mode-runner-containers" -}}
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }} {{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
{{- range $i, $container := .Values.template.spec.containers -}} {{- range $i, $container := .Values.template.spec.containers }}
{{- if ne $container.name "runner" -}} {{- if ne $container.name "runner" }}
- {{ $container | toYaml | nindent 2 }} - {{ $container | toYaml | nindent 2 }}
{{- else }} {{- else }}
- name: {{ $container.name }} - name: {{ $container.name }}
{{- range $key, $val := $container }} {{- range $key, $val := $container }}
{{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }} {{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }}
{{ $key }}: {{ $val }} {{ $key }}: {{ $val | toYaml | nindent 4 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- $setNodeExtraCaCerts := 0 }} {{- $setNodeExtraCaCerts := 0 }}
@@ -411,17 +390,12 @@ volumeMounts:
{{- with $container.env }} {{- with $container.env }}
{{- range $i, $env := . }} {{- range $i, $env := . }}
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }} {{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
{{- $setNodeExtraCaCerts = 0 -}} {{- $setNodeExtraCaCerts = 0 }}
{{- end }} {{- end }}
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }} {{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
{{- $setRunnerUpdateCaCerts = 0 -}} {{- $setRunnerUpdateCaCerts = 0 }}
{{- end }}
- name: {{ $env.name }}
{{- range $envKey, $envVal := $env }}
{{- if ne $envKey "name" }}
{{ $envKey }}: {{ $envVal | toYaml | nindent 10 }}
{{- end }}
{{- end }} {{- end }}
- {{ $env | toYaml | nindent 6 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $setNodeExtraCaCerts }} {{- if $setNodeExtraCaCerts }}
@@ -440,14 +414,9 @@ volumeMounts:
{{- with $container.volumeMounts }} {{- with $container.volumeMounts }}
{{- range $i, $volMount := . }} {{- range $i, $volMount := . }}
{{- if eq $volMount.name "github-server-tls-cert" }} {{- if eq $volMount.name "github-server-tls-cert" }}
{{- $mountGitHubServerTLS = 0 -}} {{- $mountGitHubServerTLS = 0 }}
{{- end }}
- name: {{ $volMount.name }}
{{- range $mountKey, $mountVal := $volMount }}
{{- if ne $mountKey "name" }}
{{ $mountKey }}: {{ $mountVal | toYaml | nindent 10 }}
{{- end }}
{{- end }} {{- end }}
- {{ $volMount | toYaml | nindent 6 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $mountGitHubServerTLS }} {{- if $mountGitHubServerTLS }}
@@ -458,3 +427,125 @@ volumeMounts:
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- define "gha-runner-scale-set.managerRoleName" -}}
{{- include "gha-runner-scale-set.fullname" . }}-manager-role
{{- end }}
{{- define "gha-runner-scale-set.managerRoleBinding" -}}
{{- include "gha-runner-scale-set.fullname" . }}-manager-role-binding
{{- end }}
{{- define "gha-runner-scale-set.managerServiceAccountName" -}}
{{- $searchControllerDeployment := 1 }}
{{- if .Values.controllerServiceAccount }}
{{- if .Values.controllerServiceAccount.name }}
{{- $searchControllerDeployment = 0 }}
{{- .Values.controllerServiceAccount.name }}
{{- end }}
{{- end }}
{{- if eq $searchControllerDeployment 1 }}
{{- $multiNamespacesCounter := 0 }}
{{- $singleNamespaceCounter := 0 }}
{{- $controllerDeployment := dict }}
{{- $singleNamespaceControllerDeployments := dict }}
{{- $managerServiceAccountName := "" }}
{{- range $index, $deployment := (lookup "apps/v1" "Deployment" "" "").items }}
{{- if kindIs "map" $deployment.metadata.labels }}
{{- if eq (get $deployment.metadata.labels "app.kubernetes.io/part-of") "gha-runner-scale-set-controller" }}
{{- if hasKey $deployment.metadata.labels "actions.github.com/controller-watch-single-namespace" }}
{{- $singleNamespaceCounter = add $singleNamespaceCounter 1 }}
{{- $_ := set $singleNamespaceControllerDeployments (get $deployment.metadata.labels "actions.github.com/controller-watch-single-namespace") $deployment}}
{{- else }}
{{- $multiNamespacesCounter = add $multiNamespacesCounter 1 }}
{{- $controllerDeployment = $deployment }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if and (eq $multiNamespacesCounter 0) (eq $singleNamespaceCounter 0) }}
{{- fail "No gha-runner-scale-set-controller deployment found using label (app.kubernetes.io/part-of=gha-runner-scale-set-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if and (gt $multiNamespacesCounter 0) (gt $singleNamespaceCounter 0) }}
{{- fail "Found both gha-runner-scale-set-controller installed with flags.watchSingleNamespace set and unset in cluster, this is not supported. Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if gt $multiNamespacesCounter 1 }}
{{- fail "More than one gha-runner-scale-set-controller deployment found using label (app.kubernetes.io/part-of=gha-runner-scale-set-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if eq $multiNamespacesCounter 1 }}
{{- with $controllerDeployment.metadata }}
{{- $managerServiceAccountName = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-name") }}
{{- end }}
{{- else if gt $singleNamespaceCounter 0 }}
{{- if hasKey $singleNamespaceControllerDeployments .Release.Namespace }}
{{- $controllerDeployment = get $singleNamespaceControllerDeployments .Release.Namespace }}
{{- with $controllerDeployment.metadata }}
{{- $managerServiceAccountName = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-name") }}
{{- end }}
{{- else }}
{{- fail "No gha-runner-scale-set-controller deployment that watch this namespace found using label (actions.github.com/controller-watch-single-namespace). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- end }}
{{- if eq $managerServiceAccountName "" }}
{{- fail "No service account name found for gha-runner-scale-set-controller deployment using label (actions.github.com/controller-service-account-name), consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- $managerServiceAccountName }}
{{- end }}
{{- end }}
{{- define "gha-runner-scale-set.managerServiceAccountNamespace" -}}
{{- $searchControllerDeployment := 1 }}
{{- if .Values.controllerServiceAccount }}
{{- if .Values.controllerServiceAccount.namespace }}
{{- $searchControllerDeployment = 0 }}
{{- .Values.controllerServiceAccount.namespace }}
{{- end }}
{{- end }}
{{- if eq $searchControllerDeployment 1 }}
{{- $multiNamespacesCounter := 0 }}
{{- $singleNamespaceCounter := 0 }}
{{- $controllerDeployment := dict }}
{{- $singleNamespaceControllerDeployments := dict }}
{{- $managerServiceAccountNamespace := "" }}
{{- range $index, $deployment := (lookup "apps/v1" "Deployment" "" "").items }}
{{- if kindIs "map" $deployment.metadata.labels }}
{{- if eq (get $deployment.metadata.labels "app.kubernetes.io/part-of") "gha-runner-scale-set-controller" }}
{{- if hasKey $deployment.metadata.labels "actions.github.com/controller-watch-single-namespace" }}
{{- $singleNamespaceCounter = add $singleNamespaceCounter 1 }}
{{- $_ := set $singleNamespaceControllerDeployments (get $deployment.metadata.labels "actions.github.com/controller-watch-single-namespace") $deployment}}
{{- else }}
{{- $multiNamespacesCounter = add $multiNamespacesCounter 1 }}
{{- $controllerDeployment = $deployment }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if and (eq $multiNamespacesCounter 0) (eq $singleNamespaceCounter 0) }}
{{- fail "No gha-runner-scale-set-controller deployment found using label (app.kubernetes.io/part-of=gha-runner-scale-set-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if and (gt $multiNamespacesCounter 0) (gt $singleNamespaceCounter 0) }}
{{- fail "Found both gha-runner-scale-set-controller installed with flags.watchSingleNamespace set and unset in cluster, this is not supported. Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if gt $multiNamespacesCounter 1 }}
{{- fail "More than one gha-runner-scale-set-controller deployment found using label (app.kubernetes.io/part-of=gha-runner-scale-set-controller). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- if eq $multiNamespacesCounter 1 }}
{{- with $controllerDeployment.metadata }}
{{- $managerServiceAccountNamespace = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-namespace") }}
{{- end }}
{{- else if gt $singleNamespaceCounter 0 }}
{{- if hasKey $singleNamespaceControllerDeployments .Release.Namespace }}
{{- $controllerDeployment = get $singleNamespaceControllerDeployments .Release.Namespace }}
{{- with $controllerDeployment.metadata }}
{{- $managerServiceAccountNamespace = (get $controllerDeployment.metadata.labels "actions.github.com/controller-service-account-namespace") }}
{{- end }}
{{- else }}
{{- fail "No gha-runner-scale-set-controller deployment that watch this namespace found using label (actions.github.com/controller-watch-single-namespace). Consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- end }}
{{- if eq $managerServiceAccountNamespace "" }}
{{- fail "No service account namespace found for gha-runner-scale-set-controller deployment using label (actions.github.com/controller-service-account-namespace), consider setting controllerServiceAccount.name in values.yaml to be explicit if you think the discovery is wrong." }}
{{- end }}
{{- $managerServiceAccountNamespace }}
{{- end }}
{{- end }}

View File

@@ -36,17 +36,21 @@ spec:
{{- if .Values.proxy.http }} {{- if .Values.proxy.http }}
http: http:
url: {{ .Values.proxy.http.url }} url: {{ .Values.proxy.http.url }}
{{- if .Values.proxy.http.credentialSecretRef }}
credentialSecretRef: {{ .Values.proxy.http.credentialSecretRef }} credentialSecretRef: {{ .Values.proxy.http.credentialSecretRef }}
{{ end }} {{- end }}
{{- end }}
{{- if .Values.proxy.https }} {{- if .Values.proxy.https }}
https: https:
url: {{ .Values.proxy.https.url }} url: {{ .Values.proxy.https.url }}
{{- if .Values.proxy.https.credentialSecretRef }}
credentialSecretRef: {{ .Values.proxy.https.credentialSecretRef }} credentialSecretRef: {{ .Values.proxy.https.credentialSecretRef }}
{{ end }} {{- end }}
{{- end }}
{{- if and .Values.proxy.noProxy (kindIs "slice" .Values.proxy.noProxy) }} {{- if and .Values.proxy.noProxy (kindIs "slice" .Values.proxy.noProxy) }}
noProxy: {{ .Values.proxy.noProxy | toYaml | nindent 6}} noProxy: {{ .Values.proxy.noProxy | toYaml | nindent 6}}
{{ end }} {{- end }}
{{ end }} {{- end }}
{{- if and (or (kindIs "int64" .Values.minRunners) (kindIs "float64" .Values.minRunners)) (or (kindIs "int64" .Values.maxRunners) (kindIs "float64" .Values.maxRunners)) }} {{- if and (or (kindIs "int64" .Values.minRunners) (kindIs "float64" .Values.minRunners)) (or (kindIs "int64" .Values.maxRunners) (kindIs "float64" .Values.maxRunners)) }}
{{- if gt .Values.minRunners .Values.maxRunners }} {{- if gt .Values.minRunners .Values.maxRunners }}
@@ -107,7 +111,7 @@ spec:
{{- include "gha-runner-scale-set.dind-runner-container" . | nindent 8 }} {{- include "gha-runner-scale-set.dind-runner-container" . | nindent 8 }}
- name: dind - name: dind
{{- include "gha-runner-scale-set.dind-container" . | nindent 8 }} {{- include "gha-runner-scale-set.dind-container" . | nindent 8 }}
{{- include "gha-runner-scale-set.non-runner-containers" . | nindent 6 }} {{- include "gha-runner-scale-set.non-runner-non-dind-containers" . | nindent 6 }}
{{- else if eq .Values.containerMode.type "kubernetes" }} {{- else if eq .Values.containerMode.type "kubernetes" }}
- name: runner - name: runner
{{- include "gha-runner-scale-set.kubernetes-mode-runner-container" . | nindent 8 }} {{- include "gha-runner-scale-set.kubernetes-mode-runner-container" . | nindent 8 }}

View File

@@ -0,0 +1,59 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-runner-scale-set.managerRoleName" . }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- create
- delete
- get
- apiGroups:
- ""
resources:
- pods/status
verbs:
- get
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- get
- list
- patch
- update
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- create
- delete
- get
- patch
- update
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- create
- delete
- get
- patch
- update
{{- if .Values.githubServerTLS }}
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
{{- end }}

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-runner-scale-set.managerRoleBinding" . }}
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-runner-scale-set.managerRoleName" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-runner-scale-set.managerServiceAccountName" . | nindent 4 }}
namespace: {{ include "gha-runner-scale-set.managerServiceAccountNamespace" . | nindent 4 }}

View File

@@ -27,8 +27,10 @@ func TestTemplateRenderedGitHubSecretWithGitHubToken(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -60,6 +62,8 @@ func TestTemplateRenderedGitHubSecretWithGitHubApp(t *testing.T) {
"githubConfigSecret.github_app_id": "10", "githubConfigSecret.github_app_id": "10",
"githubConfigSecret.github_app_installation_id": "100", "githubConfigSecret.github_app_installation_id": "100",
"githubConfigSecret.github_app_private_key": "private_key", "githubConfigSecret.github_app_private_key": "private_key",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -87,9 +91,11 @@ func TestTemplateRenderedGitHubSecretErrorWithMissingAuthInput(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_app_id": "", "githubConfigSecret.github_app_id": "",
"githubConfigSecret.github_token": "", "githubConfigSecret.github_token": "",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -112,8 +118,10 @@ func TestTemplateRenderedGitHubSecretErrorWithMissingAppInput(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_app_id": "10", "githubConfigSecret.github_app_id": "10",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -136,8 +144,10 @@ func TestTemplateNotRenderedGitHubSecretWithPredefinedSecret(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret": "pre-defined-secret", "githubConfigSecret": "pre-defined-secret",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -158,8 +168,10 @@ func TestTemplateRenderedSetServiceAccountToNoPermission(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -190,9 +202,11 @@ func TestTemplateRenderedSetServiceAccountToKubeMode(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"containerMode.type": "kubernetes", "containerMode.type": "kubernetes",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -248,9 +262,11 @@ func TestTemplateRenderedUserProvideSetServiceAccount(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"template.spec.serviceAccountName": "test-service-account", "template.spec.serviceAccountName": "test-service-account",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -277,8 +293,10 @@ func TestTemplateRenderedAutoScalingRunnerSet(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -322,9 +340,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_RunnerScaleSetName(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"runnerScaleSetName": "test-runner-scale-set-name", "runnerScaleSetName": "test-runner-scale-set-name",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -375,6 +395,8 @@ func TestTemplateRenderedAutoScalingRunnerSet_ProvideMetadata(t *testing.T) {
"template.metadata.labels.test2": "test2", "template.metadata.labels.test2": "test2",
"template.metadata.annotations.test3": "test3", "template.metadata.annotations.test3": "test3",
"template.metadata.annotations.test4": "test4", "template.metadata.annotations.test4": "test4",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -414,9 +436,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_MaxRunnersValidationError(t *testi
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"maxRunners": "-1", "maxRunners": "-1",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -439,10 +463,12 @@ func TestTemplateRenderedAutoScalingRunnerSet_MinRunnersValidationError(t *testi
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"maxRunners": "1", "maxRunners": "1",
"minRunners": "-1", "minRunners": "-1",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -465,10 +491,12 @@ func TestTemplateRenderedAutoScalingRunnerSet_MinMaxRunnersValidationError(t *te
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"maxRunners": "0", "maxRunners": "0",
"minRunners": "1", "minRunners": "1",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -491,10 +519,12 @@ func TestTemplateRenderedAutoScalingRunnerSet_MinMaxRunnersValidationSameValue(t
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"maxRunners": "0", "maxRunners": "0",
"minRunners": "0", "minRunners": "0",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -520,9 +550,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_MinMaxRunnersValidation_OnlyMin(t
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"minRunners": "5", "minRunners": "5",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -548,9 +580,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_MinMaxRunnersValidation_OnlyMax(t
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"maxRunners": "5", "maxRunners": "5",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -605,6 +639,10 @@ func TestTemplateRenderedAutoScalingRunnerSet_ExtraVolumes(t *testing.T) {
namespaceName := "test-" + strings.ToLower(random.UniqueId()) namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath}, ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -635,6 +673,10 @@ func TestTemplateRenderedAutoScalingRunnerSet_DinD_ExtraVolumes(t *testing.T) {
namespaceName := "test-" + strings.ToLower(random.UniqueId()) namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath}, ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -667,6 +709,10 @@ func TestTemplateRenderedAutoScalingRunnerSet_K8S_ExtraVolumes(t *testing.T) {
namespaceName := "test-" + strings.ToLower(random.UniqueId()) namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath}, ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -695,9 +741,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableDinD(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"containerMode.type": "dind", "containerMode.type": "dind",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -784,9 +832,11 @@ func TestTemplateRenderedAutoScalingRunnerSet_EnableKubernetesMode(t *testing.T)
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"containerMode.type": "kubernetes", "containerMode.type": "kubernetes",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -839,8 +889,10 @@ func TestTemplateRenderedAutoScalingRunnerSet_UsePredefinedSecret(t *testing.T)
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret": "pre-defined-secrets", "githubConfigSecret": "pre-defined-secrets",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -871,8 +923,10 @@ func TestTemplateRenderedAutoScalingRunnerSet_ErrorOnEmptyPredefinedSecret(t *te
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret": "", "githubConfigSecret": "",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -895,13 +949,15 @@ func TestTemplateRenderedWithProxy(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret": "pre-defined-secrets", "githubConfigSecret": "pre-defined-secrets",
"proxy.http.url": "http://proxy.example.com", "controllerServiceAccount.name": "arc",
"proxy.http.credentialSecretRef": "http-secret", "controllerServiceAccount.namespace": "arc-system",
"proxy.https.url": "https://proxy.example.com", "proxy.http.url": "http://proxy.example.com",
"proxy.https.credentialSecretRef": "https-secret", "proxy.http.credentialSecretRef": "http-secret",
"proxy.noProxy": "{example.com,example.org}", "proxy.https.url": "https://proxy.example.com",
"proxy.https.credentialSecretRef": "https-secret",
"proxy.noProxy": "{example.com,example.org}",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -961,6 +1017,8 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"githubServerTLS.runnerMountPath": "/runner/mount/path", "githubServerTLS.runnerMountPath": "/runner/mount/path",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1018,6 +1076,8 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"githubServerTLS.runnerMountPath": "/runner/mount/path/", "githubServerTLS.runnerMountPath": "/runner/mount/path/",
"containerMode.type": "dind", "containerMode.type": "dind",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1075,6 +1135,8 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"githubServerTLS.runnerMountPath": "/runner/mount/path", "githubServerTLS.runnerMountPath": "/runner/mount/path",
"containerMode.type": "kubernetes", "containerMode.type": "kubernetes",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1132,6 +1194,8 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubConfigSecret": "pre-defined-secrets", "githubConfigSecret": "pre-defined-secrets",
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1184,7 +1248,9 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubConfigSecret": "pre-defined-secrets", "githubConfigSecret": "pre-defined-secrets",
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"containerMode.type": "dind", "containerMode.type": "dind",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1237,7 +1303,9 @@ func TestTemplateRenderedWithTLS(t *testing.T) {
"githubConfigSecret": "pre-defined-secrets", "githubConfigSecret": "pre-defined-secrets",
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap", "githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem", "githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
"containerMode.type": "kubernetes", "containerMode.type": "kubernetes",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1293,8 +1361,10 @@ func TestTemplateNamingConstraints(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
setValues := map[string]string{ setValues := map[string]string{
"githubConfigUrl": "https://github.com/actions", "githubConfigUrl": "https://github.com/actions",
"githubConfigSecret": "", "githubConfigSecret": "",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
} }
tt := map[string]struct { tt := map[string]struct {
@@ -1339,8 +1409,10 @@ func TestTemplateRenderedGitHubConfigUrlEndsWIthSlash(t *testing.T) {
options := &helm.Options{ options := &helm.Options{
SetValues: map[string]string{ SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions/", "githubConfigUrl": "https://github.com/actions/",
"githubConfigSecret.github_token": "gh_token12345", "githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}, },
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName), KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
} }
@@ -1354,3 +1426,265 @@ func TestTemplateRenderedGitHubConfigUrlEndsWIthSlash(t *testing.T) {
assert.Equal(t, "test-runners", ars.Name) assert.Equal(t, "test-runners", ars.Name)
assert.Equal(t, "https://github.com/actions", ars.Spec.GitHubConfigUrl) assert.Equal(t, "https://github.com/actions", ars.Spec.GitHubConfigUrl)
} }
func TestTemplate_CreateManagerRole(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_role.yaml"})
var managerRole rbacv1.Role
helm.UnmarshalK8SYaml(t, output, &managerRole)
assert.Equal(t, namespaceName, managerRole.Namespace, "namespace should match the namespace of the Helm release")
assert.Equal(t, "test-runners-gha-runner-scale-set-manager-role", managerRole.Name)
assert.Equal(t, 5, len(managerRole.Rules))
}
func TestTemplate_CreateManagerRole_UseConfigMaps(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
"githubServerTLS.certificateFrom.configMapKeyRef.name": "test",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_role.yaml"})
var managerRole rbacv1.Role
helm.UnmarshalK8SYaml(t, output, &managerRole)
assert.Equal(t, namespaceName, managerRole.Namespace, "namespace should match the namespace of the Helm release")
assert.Equal(t, "test-runners-gha-runner-scale-set-manager-role", managerRole.Name)
assert.Equal(t, 6, len(managerRole.Rules))
assert.Equal(t, "configmaps", managerRole.Rules[5].Resources[0])
}
func TestTemplate_CreateManagerRoleBinding(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/manager_role_binding.yaml"})
var managerRoleBinding rbacv1.RoleBinding
helm.UnmarshalK8SYaml(t, output, &managerRoleBinding)
assert.Equal(t, namespaceName, managerRoleBinding.Namespace, "namespace should match the namespace of the Helm release")
assert.Equal(t, "test-runners-gha-runner-scale-set-manager-role-binding", managerRoleBinding.Name)
assert.Equal(t, "test-runners-gha-runner-scale-set-manager-role", managerRoleBinding.RoleRef.Name)
assert.Equal(t, "arc", managerRoleBinding.Subjects[0].Name)
assert.Equal(t, "arc-system", managerRoleBinding.Subjects[0].Namespace)
}
func TestTemplateRenderedAutoScalingRunnerSet_ExtraContainers(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
testValuesPath, err := filepath.Abs("../tests/values_extra_containers.yaml")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}, "--debug")
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
assert.Len(t, ars.Spec.Template.Spec.Containers, 2, "There should be 2 containers")
assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner")
assert.Equal(t, "other", ars.Spec.Template.Spec.Containers[1].Name, "Container name should be other")
assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), "CPU Limit should be set")
assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), "Memory Limit should be set")
assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[1].Resources.Limits.Cpu().String(), "CPU Limit should be set")
assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[1].Resources.Limits.Memory().String(), "Memory Limit should be set")
assert.Equal(t, "SOME_ENV", ars.Spec.Template.Spec.Containers[0].Env[0].Name, "SOME_ENV should be set")
assert.Equal(t, "SOME_VALUE", ars.Spec.Template.Spec.Containers[0].Env[0].Value, "SOME_ENV should be set to `SOME_VALUE`")
assert.Equal(t, "MY_NODE_NAME", ars.Spec.Template.Spec.Containers[0].Env[1].Name, "MY_NODE_NAME should be set")
assert.Equal(t, "spec.nodeName", ars.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath, "MY_NODE_NAME should be set to `spec.nodeName`")
assert.Equal(t, "work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, "VolumeMount name should be work")
assert.Equal(t, "/work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, "VolumeMount mountPath should be /work")
assert.Equal(t, "others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name, "VolumeMount name should be others")
assert.Equal(t, "/others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath, "VolumeMount mountPath should be /others")
assert.Equal(t, "work", ars.Spec.Template.Spec.Volumes[0].Name, "Volume name should be work")
assert.Equal(t, corev1.DNSNone, ars.Spec.Template.Spec.DNSPolicy, "DNS Policy should be None")
assert.Equal(t, "192.0.2.1", ars.Spec.Template.Spec.DNSConfig.Nameservers[0], "DNS Nameserver should be set")
}
func TestTemplateRenderedAutoScalingRunnerSet_ExtraPodSpec(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
testValuesPath, err := filepath.Abs("../tests/values_extra_pod_spec.yaml")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "There should be 1 containers")
assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner")
assert.Equal(t, corev1.DNSNone, ars.Spec.Template.Spec.DNSPolicy, "DNS Policy should be None")
assert.Equal(t, "192.0.2.1", ars.Spec.Template.Spec.DNSConfig.Nameservers[0], "DNS Nameserver should be set")
}
func TestTemplateRenderedAutoScalingRunnerSet_DinDMergePodSpec(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
testValuesPath, err := filepath.Abs("../tests/values_dind_merge_spec.yaml")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}, "--debug")
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
assert.Len(t, ars.Spec.Template.Spec.Containers, 2, "There should be 2 containers")
assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner")
assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), "CPU Limit should be set")
assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), "Memory Limit should be set")
assert.Equal(t, "DOCKER_HOST", ars.Spec.Template.Spec.Containers[0].Env[0].Name, "DOCKER_HOST should be set")
assert.Equal(t, "tcp://localhost:9999", ars.Spec.Template.Spec.Containers[0].Env[0].Value, "DOCKER_HOST should be set to `tcp://localhost:9999`")
assert.Equal(t, "MY_NODE_NAME", ars.Spec.Template.Spec.Containers[0].Env[1].Name, "MY_NODE_NAME should be set")
assert.Equal(t, "spec.nodeName", ars.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath, "MY_NODE_NAME should be set to `spec.nodeName`")
assert.Equal(t, "DOCKER_TLS_VERIFY", ars.Spec.Template.Spec.Containers[0].Env[2].Name, "DOCKER_TLS_VERIFY should be set")
assert.Equal(t, "1", ars.Spec.Template.Spec.Containers[0].Env[2].Value, "DOCKER_TLS_VERIFY should be set to `1`")
assert.Equal(t, "DOCKER_CERT_PATH", ars.Spec.Template.Spec.Containers[0].Env[3].Name, "DOCKER_CERT_PATH should be set")
assert.Equal(t, "/certs/client", ars.Spec.Template.Spec.Containers[0].Env[3].Value, "DOCKER_CERT_PATH should be set to `/certs/client`")
assert.Equal(t, "work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, "VolumeMount name should be work")
assert.Equal(t, "/work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, "VolumeMount mountPath should be /work")
assert.Equal(t, "others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name, "VolumeMount name should be others")
assert.Equal(t, "/others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath, "VolumeMount mountPath should be /others")
}
func TestTemplateRenderedAutoScalingRunnerSet_KubeModeMergePodSpec(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
testValuesPath, err := filepath.Abs("../tests/values_k8s_merge_spec.yaml")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
},
ValuesFiles: []string{testValuesPath},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"}, "--debug")
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "There should be 1 containers")
assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name, "Container name should be runner")
assert.Equal(t, "250m", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), "CPU Limit should be set")
assert.Equal(t, "64Mi", ars.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), "Memory Limit should be set")
assert.Equal(t, "ACTIONS_RUNNER_CONTAINER_HOOKS", ars.Spec.Template.Spec.Containers[0].Env[0].Name, "ACTIONS_RUNNER_CONTAINER_HOOKS should be set")
assert.Equal(t, "/k8s/index.js", ars.Spec.Template.Spec.Containers[0].Env[0].Value, "ACTIONS_RUNNER_CONTAINER_HOOKS should be set to `/k8s/index.js`")
assert.Equal(t, "MY_NODE_NAME", ars.Spec.Template.Spec.Containers[0].Env[1].Name, "MY_NODE_NAME should be set")
assert.Equal(t, "spec.nodeName", ars.Spec.Template.Spec.Containers[0].Env[1].ValueFrom.FieldRef.FieldPath, "MY_NODE_NAME should be set to `spec.nodeName`")
assert.Equal(t, "ACTIONS_RUNNER_POD_NAME", ars.Spec.Template.Spec.Containers[0].Env[2].Name, "ACTIONS_RUNNER_POD_NAME should be set")
assert.Equal(t, "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER", ars.Spec.Template.Spec.Containers[0].Env[3].Name, "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER should be set")
assert.Equal(t, "work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, "VolumeMount name should be work")
assert.Equal(t, "/work", ars.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, "VolumeMount mountPath should be /work")
assert.Equal(t, "others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name, "VolumeMount name should be others")
assert.Equal(t, "/others", ars.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath, "VolumeMount mountPath should be /others")
}

View File

@@ -2,4 +2,7 @@ githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret: githubConfigSecret:
github_token: test github_token: test
maxRunners: 10 maxRunners: 10
minRunners: 5 minRunners: 5
controllerServiceAccount:
name: "arc"
namespace: "arc-system"

View File

@@ -0,0 +1,31 @@
githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret:
github_token: test
template:
spec:
containers:
- name: runner
image: runner-image:latest
env:
- name: DOCKER_HOST
value: tcp://localhost:9999
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: work
mountPath: /work
- name: others
mountPath: /others
resources:
limits:
memory: "64Mi"
cpu: "250m"
volumes:
- name: work
hostPath:
path: /data
type: Directory
containerMode:
type: dind

View File

@@ -0,0 +1,46 @@
githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret:
github_token: test
template:
spec:
containers:
- name: runner
image: runner-image:latest
env:
- name: SOME_ENV
value: SOME_VALUE
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: work
mountPath: /work
- name: others
mountPath: /others
resources:
limits:
memory: "64Mi"
cpu: "250m"
- name: other
image: other-image:latest
volumeMounts:
- name: work
mountPath: /work
- name: others
mountPath: /others
resources:
limits:
memory: "64Mi"
cpu: "250m"
volumes:
- name: work
hostPath:
path: /data
type: Directory
dnsPolicy: "None"
dnsConfig:
nameservers:
- 192.0.2.1
containerMode:
type: none

View File

@@ -0,0 +1,12 @@
githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret:
github_token: test
template:
spec:
containers:
- name: runner
image: runner-image:latest
dnsPolicy: "None"
dnsConfig:
nameservers:
- 192.0.2.1

View File

@@ -0,0 +1,31 @@
githubConfigUrl: https://github.com/actions/actions-runner-controller
githubConfigSecret:
github_token: test
template:
spec:
containers:
- name: runner
image: runner-image:latest
env:
- name: ACTIONS_RUNNER_CONTAINER_HOOKS
value: /k8s/index.js
- name: MY_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: work
mountPath: /work
- name: others
mountPath: /others
resources:
limits:
memory: "64Mi"
cpu: "250m"
volumes:
- name: work
hostPath:
path: /data
type: Directory
containerMode:
type: kubernetes

View File

@@ -68,25 +68,29 @@ githubConfigSecret:
# key: ca.pem # key: ca.pem
# runnerMountPath: /usr/local/share/ca-certificates/ # runnerMountPath: /usr/local/share/ca-certificates/
## template is the PodSpec for each runner Pod
template:
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
command: ["/home/runner/run.sh"]
containerMode: containerMode:
type: "" ## type can be set to dind or kubernetes type: "" ## type can be set to dind or kubernetes
## the following is required when containerMode.type=kubernetes
# kubernetesModeWorkVolumeClaim:
# accessModes: ["ReadWriteOnce"]
# # For local testing, use https://github.com/openebs/dynamic-localpv-provisioner/blob/develop/docs/quickstart.md to provide dynamic provision volume with storageClassName: openebs-hostpath
# storageClassName: "dynamic-blob-storage"
# resources:
# requests:
# storage: 1Gi
## template is the PodSpec for each runner Pod
template:
## template.spec will be modified if you change the container mode
## with containerMode.type=dind, we will populate the template.spec with following pod spec ## with containerMode.type=dind, we will populate the template.spec with following pod spec
## template: ## template:
## spec: ## spec:
## initContainers: ## initContainers:
## - name: initExternalsInternalVolume ## - name: init-dind-externals
## image: ghcr.io/actions/actions-runner:latest ## image: ghcr.io/actions/actions-runner:latest
## command: ["cp", "-r", "-v", "/home/runner/externals/.", "/home/runner/tmpDir/"] ## command: ["cp", "-r", "-v", "/home/runner/externals/.", "/home/runner/tmpDir/"]
## volumeMounts: ## volumeMounts:
## - name: externalsInternal ## - name: dind-externals
## mountPath: /home/runner/tmpDir ## mountPath: /home/runner/tmpDir
## containers: ## containers:
## - name: runner ## - name: runner
@@ -99,9 +103,9 @@ containerMode:
## - name: DOCKER_CERT_PATH ## - name: DOCKER_CERT_PATH
## value: /certs/client ## value: /certs/client
## volumeMounts: ## volumeMounts:
## - name: workingDirectoryInternal ## - name: work
## mountPath: /home/runner/_work ## mountPath: /home/runner/_work
## - name: dinDInternal ## - name: dind-cert
## mountPath: /certs/client ## mountPath: /certs/client
## readOnly: true ## readOnly: true
## - name: dind ## - name: dind
@@ -109,18 +113,18 @@ containerMode:
## securityContext: ## securityContext:
## privileged: true ## privileged: true
## volumeMounts: ## volumeMounts:
## - mountPath: /certs/client ## - name: work
## name: dinDInternal ## mountPath: /home/runner/_work
## - mountPath: /home/runner/_work ## - name: dind-cert
## name: workingDirectoryInternal ## mountPath: /certs/client
## - mountPath: /home/runner/externals ## - name: dind-externals
## name: externalsInternal ## mountPath: /home/runner/externals
## volumes: ## volumes:
## - name: dinDInternal ## - name: work
## emptyDir: {} ## emptyDir: {}
## - name: workingDirectoryInternal ## - name: dind-cert
## emptyDir: {} ## emptyDir: {}
## - name: externalsInternal ## - name: dind-externals
## emptyDir: {} ## emptyDir: {}
###################################################################################################### ######################################################################################################
## with containerMode.type=kubernetes, we will populate the template.spec with following pod spec ## with containerMode.type=kubernetes, we will populate the template.spec with following pod spec
@@ -151,13 +155,18 @@ containerMode:
## resources: ## resources:
## requests: ## requests:
## storage: 1Gi ## storage: 1Gi
spec:
containers:
- name: runner
image: ghcr.io/actions/actions-runner:latest
command: ["/home/runner/run.sh"]
## the following is required when containerMode.type=kubernetes ## Optional controller service account that needs to have required Role and RoleBinding
kubernetesModeWorkVolumeClaim: ## to operate this gha-runner-scale-set installation.
accessModes: ["ReadWriteOnce"] ## The helm chart will try to find the controller deployment and its service account at installation time.
# For testing, use https://github.com/rancher/local-path-provisioner to provide dynamic provision volume ## In case the helm chart can't find the right service account, you can explicitly pass in the following value
# TODO: remove before release ## to help it finish RoleBinding with the right service account.
storageClassName: "dynamic-blob-storage" ## Note: if your controller is installed to only watch a single namespace, you have to pass these values explicitly.
resources: # controllerServiceAccount:
requests: # namespace: arc-system
storage: 1Gi # name: test-arc-gha-runner-scale-set-controller

View File

@@ -144,7 +144,7 @@ func TestCustomerServerRootCA(t *testing.T) {
client, err := newActionsClientFromConfig(config, creds) client, err := newActionsClientFromConfig(config, creds)
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, "test") _, err = client.GetRunnerScaleSet(ctx, 1, "test")
require.NoError(t, err) require.NoError(t, err)
assert.True(t, serverCalledSuccessfully) assert.True(t, serverCalledSuccessfully)
} }

View File

@@ -17,16 +17,28 @@ spec:
- additionalPrinterColumns: - additionalPrinterColumns:
- jsonPath: .spec.minRunners - jsonPath: .spec.minRunners
name: Minimum Runners name: Minimum Runners
type: number type: integer
- jsonPath: .spec.maxRunners - jsonPath: .spec.maxRunners
name: Maximum Runners name: Maximum Runners
type: number type: integer
- jsonPath: .status.currentRunners - jsonPath: .status.currentRunners
name: Current Runners name: Current Runners
type: number type: integer
- jsonPath: .status.state - jsonPath: .status.state
name: State name: State
type: string type: string
- jsonPath: .status.pendingEphemeralRunners
name: Pending Runners
type: integer
- jsonPath: .status.runningEphemeralRunners
name: Running Runners
type: integer
- jsonPath: .status.finishedEphemeralRunners
name: Finished Runners
type: integer
- jsonPath: .status.deletingEphemeralRunners
name: Deleting Runners
type: integer
name: v1alpha1 name: v1alpha1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
@@ -4306,6 +4318,12 @@ spec:
properties: properties:
currentRunners: currentRunners:
type: integer type: integer
failedEphemeralRunners:
type: integer
pendingEphemeralRunners:
type: integer
runningEphemeralRunners:
type: integer
state: state:
type: string type: string
type: object type: object

View File

@@ -21,6 +21,18 @@ spec:
- jsonPath: .status.currentReplicas - jsonPath: .status.currentReplicas
name: CurrentReplicas name: CurrentReplicas
type: integer type: integer
- jsonPath: .status.pendingEphemeralRunners
name: Pending Runners
type: integer
- jsonPath: .status.runningEphemeralRunners
name: Running Runners
type: integer
- jsonPath: .status.finishedEphemeralRunners
name: Finished Runners
type: integer
- jsonPath: .status.deletingEphemeralRunners
name: Deleting Runners
type: integer
name: v1alpha1 name: v1alpha1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
@@ -4296,6 +4308,14 @@ spec:
currentReplicas: currentReplicas:
description: CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet. description: CurrentReplicas is the number of currently running EphemeralRunner resources being managed by this EphemeralRunnerSet.
type: integer type: integer
failedEphemeralRunners:
type: integer
pendingEphemeralRunners:
type: integer
runningEphemeralRunners:
type: integer
required:
- currentReplicas
type: object type: object
type: object type: object
served: true served: true

View File

@@ -0,0 +1,10 @@
source:
kind: Deployment
name: controller-manager
fieldPath: spec.template.spec.containers.[name=manager].image
targets:
- select:
kind: Deployment
name: controller-manager
fieldPaths:
- spec.template.spec.containers.[name=manager].env.[name=CONTROLLER_MANAGER_CONTAINER_IMAGE].value

View File

@@ -6,3 +6,6 @@ images:
- name: controller - name: controller
newName: summerwind/actions-runner-controller newName: summerwind/actions-runner-controller
newTag: dev newTag: dev
replacements:
- path: env-replacement.yaml

View File

@@ -50,10 +50,8 @@ spec:
optional: true optional: true
- name: GITHUB_APP_PRIVATE_KEY - name: GITHUB_APP_PRIVATE_KEY
value: /etc/actions-runner-controller/github_app_private_key value: /etc/actions-runner-controller/github_app_private_key
- name: CONTROLLER_MANAGER_POD_NAME - name: CONTROLLER_MANAGER_CONTAINER_IMAGE
valueFrom: value: CONTROLLER_MANAGER_CONTAINER_IMAGE
fieldRef:
fieldPath: metadata.name
- name: CONTROLLER_MANAGER_POD_NAMESPACE - name: CONTROLLER_MANAGER_POD_NAMESPACE
valueFrom: valueFrom:
fieldRef: fieldRef:

View File

@@ -86,7 +86,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
} }
if !done { if !done {
log.Info("Waiting for resources to be deleted before removing finalizer") log.Info("Waiting for resources to be deleted before removing finalizer")
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
log.Info("Removing finalizer") log.Info("Removing finalizer")
@@ -204,7 +204,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
return r.createRoleBindingForListener(ctx, autoscalingListener, listenerRole, serviceAccount, log) return r.createRoleBindingForListener(ctx, autoscalingListener, listenerRole, serviceAccount, log)
} }
// Create a secret containing proxy config if specifiec // Create a secret containing proxy config if specified
if autoscalingListener.Spec.Proxy != nil { if autoscalingListener.Spec.Proxy != nil {
proxySecret := new(corev1.Secret) proxySecret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: proxyListenerSecretName(autoscalingListener)}, proxySecret); err != nil { if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingListener.Namespace, Name: proxyListenerSecretName(autoscalingListener)}, proxySecret); err != nil {
@@ -299,7 +299,6 @@ func (r *AutoscalingListenerReconciler) SetupWithManager(mgr ctrl.Manager) error
For(&v1alpha1.AutoscalingListener{}). For(&v1alpha1.AutoscalingListener{}).
Owns(&corev1.Pod{}). Owns(&corev1.Pod{}).
Owns(&corev1.ServiceAccount{}). Owns(&corev1.ServiceAccount{}).
Owns(&corev1.Secret{}).
Watches(&source.Kind{Type: &rbacv1.Role{}}, handler.EnqueueRequestsFromMapFunc(labelBasedWatchFunc)). Watches(&source.Kind{Type: &rbacv1.Role{}}, handler.EnqueueRequestsFromMapFunc(labelBasedWatchFunc)).
Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler.EnqueueRequestsFromMapFunc(labelBasedWatchFunc)). Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler.EnqueueRequestsFromMapFunc(labelBasedWatchFunc)).
WithEventFilter(predicate.ResourceVersionChangedPredicate{}). WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
@@ -503,7 +502,7 @@ func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Con
} }
logger.Info("Created listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name) logger.Info("Created listener secret", "namespace", newListenerSecret.Namespace, "name", newListenerSecret.Name)
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {
@@ -542,7 +541,7 @@ func (r *AutoscalingListenerReconciler) createProxySecret(ctx context.Context, a
logger.Info("Created listener proxy secret", "namespace", newProxySecret.Namespace, "name", newProxySecret.Name) logger.Info("Created listener proxy secret", "namespace", newProxySecret.Namespace, "name", newProxySecret.Name)
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Context, secret *corev1.Secret, mirrorSecret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) { func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Context, secret *corev1.Secret, mirrorSecret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
@@ -558,7 +557,7 @@ func (r *AutoscalingListenerReconciler) updateSecretsForListener(ctx context.Con
} }
logger.Info("Updated listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash) logger.Info("Updated listener mirror secret", "namespace", updatedMirrorSecret.Namespace, "name", updatedMirrorSecret.Name, "hash", dataHash)
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) { func (r *AutoscalingListenerReconciler) createRoleForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, logger logr.Logger) (ctrl.Result, error) {

View File

@@ -238,6 +238,9 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
if latestRunnerSet.Status.CurrentReplicas != autoscalingRunnerSet.Status.CurrentRunners { if latestRunnerSet.Status.CurrentReplicas != autoscalingRunnerSet.Status.CurrentRunners {
if err := patchSubResource(ctx, r.Status(), autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) { if err := patchSubResource(ctx, r.Status(), autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
obj.Status.CurrentRunners = latestRunnerSet.Status.CurrentReplicas obj.Status.CurrentRunners = latestRunnerSet.Status.CurrentReplicas
obj.Status.PendingEphemeralRunners = latestRunnerSet.Status.PendingEphemeralRunners
obj.Status.RunningEphemeralRunners = latestRunnerSet.Status.RunningEphemeralRunners
obj.Status.FailedEphemeralRunners = latestRunnerSet.Status.FailedEphemeralRunners
}); err != nil { }); err != nil {
log.Error(err, "Failed to update autoscaling runner set status with current runner count") log.Error(err, "Failed to update autoscaling runner set status with current runner count")
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -313,24 +316,29 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set") logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
runnerScaleSet, err := actionsClient.GetRunnerScaleSet(ctx, autoscalingRunnerSet.Spec.RunnerScaleSetName)
runnerGroupId := 1
if len(autoscalingRunnerSet.Spec.RunnerGroup) > 0 {
runnerGroup, err := actionsClient.GetRunnerGroupByName(ctx, autoscalingRunnerSet.Spec.RunnerGroup)
if err != nil {
logger.Error(err, "Failed to get runner group by name", "runnerGroup", autoscalingRunnerSet.Spec.RunnerGroup)
return ctrl.Result{}, err
}
runnerGroupId = int(runnerGroup.ID)
}
runnerScaleSet, err := actionsClient.GetRunnerScaleSet(ctx, runnerGroupId, autoscalingRunnerSet.Spec.RunnerScaleSetName)
if err != nil { if err != nil {
logger.Error(err, "Failed to get runner scale set from Actions service") logger.Error(err, "Failed to get runner scale set from Actions service",
"runnerGroupId",
strconv.Itoa(runnerGroupId),
"runnerScaleSetName",
autoscalingRunnerSet.Spec.RunnerScaleSetName)
return ctrl.Result{}, err return ctrl.Result{}, err
} }
runnerGroupId := 1
if runnerScaleSet == nil { if runnerScaleSet == nil {
if len(autoscalingRunnerSet.Spec.RunnerGroup) > 0 {
runnerGroup, err := actionsClient.GetRunnerGroupByName(ctx, autoscalingRunnerSet.Spec.RunnerGroup)
if err != nil {
logger.Error(err, "Failed to get runner group by name", "runnerGroup", autoscalingRunnerSet.Spec.RunnerGroup)
return ctrl.Result{}, err
}
runnerGroupId = int(runnerGroup.ID)
}
runnerScaleSet, err = actionsClient.CreateRunnerScaleSet( runnerScaleSet, err = actionsClient.CreateRunnerScaleSet(
ctx, ctx,
&actions.RunnerScaleSet{ &actions.RunnerScaleSet{

View File

@@ -157,23 +157,6 @@ var _ = Describe("Test AutoScalingRunnerSet controller", func() {
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace)) err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
Expect(len(runnerSetList.Items)).To(BeEquivalentTo(1), "Only one EphemeralRunnerSet should be created") Expect(len(runnerSetList.Items)).To(BeEquivalentTo(1), "Only one EphemeralRunnerSet should be created")
runnerSet := runnerSetList.Items[0]
statusUpdate := runnerSet.DeepCopy()
statusUpdate.Status.CurrentReplicas = 100
err = k8sClient.Status().Patch(ctx, statusUpdate, client.MergeFrom(&runnerSet))
Expect(err).NotTo(HaveOccurred(), "failed to patch EphemeralRunnerSet status")
Eventually(
func() (int, error) {
updated := new(v1alpha1.AutoscalingRunnerSet)
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
if err != nil {
return 0, fmt.Errorf("failed to get AutoScalingRunnerSet: %w", err)
}
return updated.Status.CurrentRunners, nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(100), "AutoScalingRunnerSet status should be updated")
}) })
}) })
@@ -398,9 +381,75 @@ var _ = Describe("Test AutoScalingRunnerSet controller", func() {
return updated.Annotations[runnerScaleSetRunnerGroupNameKey], nil return updated.Annotations[runnerScaleSetRunnerGroupNameKey], nil
}, },
autoscalingRunnerSetTestTimeout, autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo("testgroup2"), "AutoScalingRunnerSet should have the runner group in its annotation") autoscalingRunnerSetTestInterval,
).Should(BeEquivalentTo("testgroup2"), "AutoScalingRunnerSet should have the runner group in its annotation")
}) })
}) })
It("Should update Status on EphemeralRunnerSet status Update", func() {
ars := new(v1alpha1.AutoscalingRunnerSet)
Eventually(
func() (bool, error) {
err := k8sClient.Get(
ctx,
client.ObjectKey{
Name: autoscalingRunnerSet.Name,
Namespace: autoscalingRunnerSet.Namespace,
},
ars,
)
if err != nil {
return false, err
}
return true, nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(BeTrue(), "AutoscalingRunnerSet should be created")
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
Eventually(func() (int, error) {
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(ars.Namespace))
if err != nil {
return 0, err
}
return len(runnerSetList.Items), nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(BeEquivalentTo(1), "Failed to fetch runner set list")
runnerSet := runnerSetList.Items[0]
statusUpdate := runnerSet.DeepCopy()
statusUpdate.Status.CurrentReplicas = 6
statusUpdate.Status.FailedEphemeralRunners = 1
statusUpdate.Status.RunningEphemeralRunners = 2
statusUpdate.Status.PendingEphemeralRunners = 3
desiredStatus := v1alpha1.AutoscalingRunnerSetStatus{
CurrentRunners: statusUpdate.Status.CurrentReplicas,
State: "",
PendingEphemeralRunners: statusUpdate.Status.PendingEphemeralRunners,
RunningEphemeralRunners: statusUpdate.Status.RunningEphemeralRunners,
FailedEphemeralRunners: statusUpdate.Status.FailedEphemeralRunners,
}
err := k8sClient.Status().Patch(ctx, statusUpdate, client.MergeFrom(&runnerSet))
Expect(err).NotTo(HaveOccurred(), "Failed to patch runner set status")
Eventually(
func() (v1alpha1.AutoscalingRunnerSetStatus, error) {
updated := new(v1alpha1.AutoscalingRunnerSet)
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
if err != nil {
return v1alpha1.AutoscalingRunnerSetStatus{}, fmt.Errorf("failed to get AutoScalingRunnerSet: %w", err)
}
return updated.Status, nil
},
autoscalingRunnerSetTestTimeout,
autoscalingRunnerSetTestInterval,
).Should(BeEquivalentTo(desiredStatus), "AutoScalingRunnerSet status should be updated")
})
}) })
var _ = Describe("Test AutoScalingController updates", func() { var _ = Describe("Test AutoScalingController updates", func() {

View File

@@ -107,7 +107,7 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
} }
if !done { if !done {
log.Info("Waiting for ephemeral runner owned resources to be deleted") log.Info("Waiting for ephemeral runner owned resources to be deleted")
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
done, err = r.cleanupContainerHooksResources(ctx, ephemeralRunner, log) done, err = r.cleanupContainerHooksResources(ctx, ephemeralRunner, log)
@@ -643,7 +643,7 @@ func (r *EphemeralRunnerReconciler) createSecret(ctx context.Context, runner *v1
} }
log.Info("Created ephemeral runner secret", "secretName", jitSecret.Name) log.Info("Created ephemeral runner secret", "secretName", jitSecret.Name)
return ctrl.Result{}, nil return ctrl.Result{Requeue: true}, nil
} }
// updateRunStatusFromPod is responsible for updating non-exiting statuses. // updateRunStatusFromPod is responsible for updating non-exiting statuses.
@@ -792,7 +792,6 @@ func (r *EphemeralRunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr). return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.EphemeralRunner{}). For(&v1alpha1.EphemeralRunner{}).
Owns(&corev1.Pod{}). Owns(&corev1.Pod{}).
Owns(&corev1.Secret{}).
WithEventFilter(predicate.ResourceVersionChangedPredicate{}). WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
Named("ephemeral-runner-controller"). Named("ephemeral-runner-controller").
Complete(r) Complete(r)

View File

@@ -200,11 +200,18 @@ func (r *EphemeralRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl.R
} }
} }
desiredStatus := v1alpha1.EphemeralRunnerSetStatus{
CurrentReplicas: total,
PendingEphemeralRunners: len(pendingEphemeralRunners),
RunningEphemeralRunners: len(runningEphemeralRunners),
FailedEphemeralRunners: len(failedEphemeralRunners),
}
// Update the status if needed. // Update the status if needed.
if ephemeralRunnerSet.Status.CurrentReplicas != total { if ephemeralRunnerSet.Status != desiredStatus {
log.Info("Updating status with current runners count", "count", total) log.Info("Updating status with current runners count", "count", total)
if err := patchSubResource(ctx, r.Status(), ephemeralRunnerSet, func(obj *v1alpha1.EphemeralRunnerSet) { if err := patchSubResource(ctx, r.Status(), ephemeralRunnerSet, func(obj *v1alpha1.EphemeralRunnerSet) {
obj.Status.CurrentReplicas = total obj.Status = desiredStatus
}); err != nil { }); err != nil {
log.Error(err, "Failed to update status with current runners count") log.Error(err, "Failed to update status with current runners count")
return ctrl.Result{}, err return ctrl.Result{}, err

View File

@@ -559,6 +559,181 @@ var _ = Describe("Test EphemeralRunnerSet controller", func() {
ephemeralRunnerSetTestTimeout, ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval).Should(BeEquivalentTo(0), "0 EphemeralRunner should be created") ephemeralRunnerSetTestInterval).Should(BeEquivalentTo(0), "0 EphemeralRunner should be created")
}) })
It("Should update status on Ephemeral Runner state changes", func() {
created := new(actionsv1alpha1.EphemeralRunnerSet)
Eventually(
func() error {
return k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunnerSet.Name, Namespace: ephemeralRunnerSet.Namespace}, created)
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(Succeed(), "EphemeralRunnerSet should be created")
// Scale up the EphemeralRunnerSet
updated := created.DeepCopy()
updated.Spec.Replicas = 3
err := k8sClient.Update(ctx, updated)
Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet replica count")
runnerList := new(actionsv1alpha1.EphemeralRunnerList)
Eventually(
func() (bool, error) {
err := k8sClient.List(ctx, runnerList, client.InNamespace(ephemeralRunnerSet.Namespace))
if err != nil {
return false, err
}
if len(runnerList.Items) != 3 {
return false, err
}
var pendingOriginal *v1alpha1.EphemeralRunner
var runningOriginal *v1alpha1.EphemeralRunner
var failedOriginal *v1alpha1.EphemeralRunner
var empty []*v1alpha1.EphemeralRunner
for _, runner := range runnerList.Items {
switch runner.Status.RunnerId {
case 101:
pendingOriginal = runner.DeepCopy()
case 102:
runningOriginal = runner.DeepCopy()
case 103:
failedOriginal = runner.DeepCopy()
default:
empty = append(empty, runner.DeepCopy())
}
}
refetch := false
if pendingOriginal == nil { // if NO pending
refetch = true
pendingOriginal = empty[0]
empty = empty[1:]
pending := pendingOriginal.DeepCopy()
pending.Status.RunnerId = 101
pending.Status.Phase = corev1.PodPending
err = k8sClient.Status().Patch(ctx, pending, client.MergeFrom(pendingOriginal))
if err != nil {
return false, err
}
}
if runningOriginal == nil { // if NO running
refetch = true
runningOriginal = empty[0]
empty = empty[1:]
running := runningOriginal.DeepCopy()
running.Status.RunnerId = 102
running.Status.Phase = corev1.PodRunning
err = k8sClient.Status().Patch(ctx, running, client.MergeFrom(runningOriginal))
if err != nil {
return false, err
}
}
if failedOriginal == nil { // if NO failed
refetch = true
failedOriginal = empty[0]
failed := pendingOriginal.DeepCopy()
failed.Status.RunnerId = 103
failed.Status.Phase = corev1.PodFailed
err = k8sClient.Status().Patch(ctx, failed, client.MergeFrom(failedOriginal))
if err != nil {
return false, err
}
}
return !refetch, nil
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(BeTrue(), "Failed to eventually update to one pending, one running and one failed")
desiredStatus := v1alpha1.EphemeralRunnerSetStatus{
CurrentReplicas: 3,
PendingEphemeralRunners: 1,
RunningEphemeralRunners: 1,
FailedEphemeralRunners: 1,
}
Eventually(
func() (v1alpha1.EphemeralRunnerSetStatus, error) {
updated := new(v1alpha1.EphemeralRunnerSet)
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunnerSet.Name, Namespace: ephemeralRunnerSet.Namespace}, updated)
if err != nil {
return v1alpha1.EphemeralRunnerSetStatus{}, err
}
return updated.Status, nil
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(BeEquivalentTo(desiredStatus), "Status is not eventually updated to the desired one")
updated = new(v1alpha1.EphemeralRunnerSet)
err = k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunnerSet.Name, Namespace: ephemeralRunnerSet.Namespace}, updated)
Expect(err).NotTo(HaveOccurred(), "Failed to fetch ephemeral runner set")
updatedOriginal := updated.DeepCopy()
updated.Spec.Replicas = 0
err = k8sClient.Patch(ctx, updated, client.MergeFrom(updatedOriginal))
Expect(err).NotTo(HaveOccurred(), "Failed to patch ephemeral runner set with 0 replicas")
Eventually(
func() (int, error) {
runnerList = new(actionsv1alpha1.EphemeralRunnerList)
err := k8sClient.List(ctx, runnerList, client.InNamespace(ephemeralRunnerSet.Namespace))
if err != nil {
return -1, err
}
return len(runnerList.Items), nil
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(BeEquivalentTo(1), "Failed to eventually scale down")
desiredStatus = v1alpha1.EphemeralRunnerSetStatus{
CurrentReplicas: 1,
PendingEphemeralRunners: 0,
RunningEphemeralRunners: 0,
FailedEphemeralRunners: 1,
}
Eventually(
func() (v1alpha1.EphemeralRunnerSetStatus, error) {
updated := new(v1alpha1.EphemeralRunnerSet)
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunnerSet.Name, Namespace: ephemeralRunnerSet.Namespace}, updated)
if err != nil {
return v1alpha1.EphemeralRunnerSetStatus{}, err
}
return updated.Status, nil
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(BeEquivalentTo(desiredStatus), "Status is not eventually updated to the desired one")
err = k8sClient.Delete(ctx, &runnerList.Items[0])
Expect(err).To(BeNil(), "Failed to delete failed ephemeral runner")
desiredStatus = v1alpha1.EphemeralRunnerSetStatus{} // empty
Eventually(
func() (v1alpha1.EphemeralRunnerSetStatus, error) {
updated := new(v1alpha1.EphemeralRunnerSet)
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunnerSet.Name, Namespace: ephemeralRunnerSet.Namespace}, updated)
if err != nil {
return v1alpha1.EphemeralRunnerSetStatus{}, err
}
return updated.Status, nil
},
ephemeralRunnerSetTestTimeout,
ephemeralRunnerSetTestInterval,
).Should(BeEquivalentTo(desiredStatus), "Status is not eventually updated to the desired one")
})
}) })
}) })
@@ -821,12 +996,13 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0])) err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0]))
Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status") Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status")
updatedRunnerSet := new(actionsv1alpha1.EphemeralRunnerSet) runnerSet := new(actionsv1alpha1.EphemeralRunnerSet)
err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, updatedRunnerSet) err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, runnerSet)
Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet")
updatedRunnerSet := runnerSet.DeepCopy()
updatedRunnerSet.Spec.Replicas = 0 updatedRunnerSet.Spec.Replicas = 0
err = k8sClient.Update(ctx, updatedRunnerSet) err = k8sClient.Patch(ctx, updatedRunnerSet, client.MergeFrom(runnerSet))
Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet")
Eventually( Eventually(
@@ -965,12 +1141,13 @@ var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func(
err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0])) err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0]))
Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status") Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status")
updatedRunnerSet := new(actionsv1alpha1.EphemeralRunnerSet) currentRunnerSet := new(actionsv1alpha1.EphemeralRunnerSet)
err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, updatedRunnerSet) err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, currentRunnerSet)
Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet")
updatedRunnerSet := currentRunnerSet.DeepCopy()
updatedRunnerSet.Spec.Replicas = 0 updatedRunnerSet.Spec.Replicas = 0
err = k8sClient.Update(ctx, updatedRunnerSet) err = k8sClient.Patch(ctx, updatedRunnerSet, client.MergeFrom(currentRunnerSet))
Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet") Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet")
// wait for server to be called // wait for server to be called

View File

@@ -124,6 +124,31 @@ https://user-images.githubusercontent.com/568794/212668313-8946ddc5-60c1-461f-a7
arc-runners arc-runner-set-rmrgw-runner-p9p5n 1/1 Running 0 21s arc-runners arc-runner-set-rmrgw-runner-p9p5n 1/1 Running 0 21s
``` ```
### Upgrade to newer versions
Upgrading actions-runner-controller requires a few extra steps because CRDs will not be automatically upgraded (this is a helm limitation).
1. Uninstall the autoscaling runner set first
```bash
INSTALLATION_NAME="arc-runner-set"
NAMESPACE="arc-runners"
helm uninstall "${INSTALLATION_NAME}" --namespace "${NAMESPACE}"
```
1. Wait for all the pods to drain
1. Pull the new helm chart, unpack it and update the CRDs. When applying this step, don't forget to replace `<PATH>` with the path of the `gha-runner-scale-set-controller` helm chart:
```bash
helm pull oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \
--version 0.3.0 \
--untar && \
kubectl replace -f <PATH>/gha-runner-scale-set-controller/crds/
```
1. Reinstall actions-runner-controller using the steps from the previous section
## Troubleshooting ## Troubleshooting
### Check the logs ### Check the logs

View File

@@ -31,7 +31,7 @@ const (
//go:generate mockery --inpackage --name=ActionsService //go:generate mockery --inpackage --name=ActionsService
type ActionsService interface { type ActionsService interface {
GetRunnerScaleSet(ctx context.Context, runnerScaleSetName string) (*RunnerScaleSet, error) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error)
GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error) GetRunnerScaleSetById(ctx context.Context, runnerScaleSetId int) (*RunnerScaleSet, error)
GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error) GetRunnerGroupByName(ctx context.Context, runnerGroup string) (*RunnerGroup, error)
CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error) CreateRunnerScaleSet(ctx context.Context, runnerScaleSet *RunnerScaleSet) (*RunnerScaleSet, error)
@@ -285,8 +285,8 @@ func (c *Client) NewActionsServiceRequest(ctx context.Context, method, path stri
return req, nil return req, nil
} }
func (c *Client) GetRunnerScaleSet(ctx context.Context, runnerScaleSetName string) (*RunnerScaleSet, error) { func (c *Client) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error) {
path := fmt.Sprintf("/%s?name=%s", scaleSetEndpoint, runnerScaleSetName) path := fmt.Sprintf("/%s?runnerGroupId=%d&name=%s", scaleSetEndpoint, runnerGroupId, runnerScaleSetName)
req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil) req, err := c.NewActionsServiceRequest(ctx, http.MethodGet, path, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -34,7 +34,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
got, err := client.GetRunnerScaleSet(ctx, scaleSetName) got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, want, got) assert.Equal(t, want, got)
}) })
@@ -50,7 +50,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, scaleSetName) _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
require.NoError(t, err) require.NoError(t, err)
expectedPath := "/tenant/123/_apis/runtime/runnerscalesets" expectedPath := "/tenant/123/_apis/runtime/runnerscalesets"
@@ -67,7 +67,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, scaleSetName) _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
@@ -80,7 +80,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, scaleSetName) _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
assert.NotNil(t, err) assert.NotNil(t, err)
}) })
@@ -102,7 +102,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, scaleSetName) _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
assert.NotNil(t, err) assert.NotNil(t, err)
expectedRetry := retryMax + 1 expectedRetry := retryMax + 1
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry) assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
@@ -118,7 +118,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
got, err := client.GetRunnerScaleSet(ctx, scaleSetName) got, err := client.GetRunnerScaleSet(ctx, 1, scaleSetName)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, want, got) assert.Equal(t, want, got)
}) })
@@ -133,7 +133,7 @@ func TestGetRunnerScaleSet(t *testing.T) {
client, err := actions.NewClient(server.configURLForOrg("my-org"), auth) client, err := actions.NewClient(server.configURLForOrg("my-org"), auth)
require.NoError(t, err) require.NoError(t, err)
_, err = client.GetRunnerScaleSet(ctx, scaleSetName) _, err = client.GetRunnerScaleSet(ctx, 1, scaleSetName)
require.NotNil(t, err) require.NotNil(t, err)
assert.Equal(t, wantErr.Error(), err.Error()) assert.Equal(t, wantErr.Error(), err.Error())
}) })

View File

@@ -215,7 +215,7 @@ func (f *FakeClient) applyDefaults() {
f.getRunnerByNameResult.RunnerReference = defaultRunnerReference f.getRunnerByNameResult.RunnerReference = defaultRunnerReference
} }
func (f *FakeClient) GetRunnerScaleSet(ctx context.Context, runnerScaleSetName string) (*actions.RunnerScaleSet, error) { func (f *FakeClient) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*actions.RunnerScaleSet, error) {
return f.getRunnerScaleSetResult.RunnerScaleSet, f.getRunnerScaleSetResult.err return f.getRunnerScaleSetResult.RunnerScaleSet, f.getRunnerScaleSetResult.err
} }

View File

@@ -263,13 +263,13 @@ func (_m *MockActionsService) GetRunnerGroupByName(ctx context.Context, runnerGr
return r0, r1 return r0, r1
} }
// GetRunnerScaleSet provides a mock function with given fields: ctx, runnerScaleSetName // GetRunnerScaleSet provides a mock function with given fields: ctx, runnerGroupId, runnerScaleSetName
func (_m *MockActionsService) GetRunnerScaleSet(ctx context.Context, runnerScaleSetName string) (*RunnerScaleSet, error) { func (_m *MockActionsService) GetRunnerScaleSet(ctx context.Context, runnerGroupId int, runnerScaleSetName string) (*RunnerScaleSet, error) {
ret := _m.Called(ctx, runnerScaleSetName) ret := _m.Called(ctx, runnerGroupId, runnerScaleSetName)
var r0 *RunnerScaleSet var r0 *RunnerScaleSet
if rf, ok := ret.Get(0).(func(context.Context, string) *RunnerScaleSet); ok { if rf, ok := ret.Get(0).(func(context.Context, int, string) *RunnerScaleSet); ok {
r0 = rf(ctx, runnerScaleSetName) r0 = rf(ctx, runnerGroupId, runnerScaleSetName)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*RunnerScaleSet) r0 = ret.Get(0).(*RunnerScaleSet)
@@ -277,8 +277,8 @@ func (_m *MockActionsService) GetRunnerScaleSet(ctx context.Context, runnerScale
} }
var r1 error var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { if rf, ok := ret.Get(1).(func(context.Context, int, string) error); ok {
r1 = rf(ctx, runnerScaleSetName) r1 = rf(ctx, runnerGroupId, runnerScaleSetName)
} else { } else {
r1 = ret.Error(1) r1 = ret.Error(1)
} }

189
main.go
View File

@@ -17,7 +17,6 @@ limitations under the License.
package main package main
import ( import (
"context"
"flag" "flag"
"fmt" "fmt"
"os" "os"
@@ -35,10 +34,11 @@ import (
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
clientgoscheme "k8s.io/client-go/kubernetes/scheme" clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
@@ -47,9 +47,7 @@ const (
defaultDockerImage = "docker:dind" defaultDockerImage = "docker:dind"
) )
var ( var scheme = runtime.NewScheme()
scheme = runtime.NewScheme()
)
func init() { func init() {
_ = clientgoscheme.AddToScheme(scheme) _ = clientgoscheme.AddToScheme(scheme)
@@ -68,6 +66,7 @@ func (i *stringSlice) Set(value string) error {
*i = append(*i, value) *i = append(*i, value)
return nil return nil
} }
func main() { func main() {
var ( var (
err error err error
@@ -92,6 +91,7 @@ func main() {
namespace string namespace string
logLevel string logLevel string
logFormat string logFormat string
watchSingleNamespace string
autoScalerImagePullSecrets stringSlice autoScalerImagePullSecrets stringSlice
@@ -128,6 +128,7 @@ func main() {
flag.DurationVar(&syncPeriod, "sync-period", 1*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled.") flag.DurationVar(&syncPeriod, "sync-period", 1*time.Minute, "Determines the minimum frequency at which K8s resources managed by this controller are reconciled.")
flag.Var(&commonRunnerLabels, "common-runner-labels", "Runner labels in the K1=V1,K2=V2,... format that are inherited all the runners created by the controller. See https://github.com/actions/actions-runner-controller/issues/321 for more information") flag.Var(&commonRunnerLabels, "common-runner-labels", "Runner labels in the K1=V1,K2=V2,... format that are inherited all the runners created by the controller. See https://github.com/actions/actions-runner-controller/issues/321 for more information")
flag.StringVar(&namespace, "watch-namespace", "", "The namespace to watch for custom resources. Set to empty for letting it watch for all namespaces.") flag.StringVar(&namespace, "watch-namespace", "", "The namespace to watch for custom resources. Set to empty for letting it watch for all namespaces.")
flag.StringVar(&watchSingleNamespace, "watch-single-namespace", "", "Restrict to watch for custom resources in a single namespace.")
flag.StringVar(&logLevel, "log-level", logging.LogLevelDebug, `The verbosity of the logging. Valid values are "debug", "info", "warn", "error". Defaults to "debug".`) flag.StringVar(&logLevel, "log-level", logging.LogLevelDebug, `The verbosity of the logging. Valid values are "debug", "info", "warn", "error". Defaults to "debug".`)
flag.StringVar(&logFormat, "log-format", "text", `The log format. Valid options are "text" and "json". Defaults to "text"`) flag.StringVar(&logFormat, "log-format", "text", `The log format. Valid options are "text" and "json". Defaults to "text"`)
flag.BoolVar(&autoScalingRunnerSetOnly, "auto-scaling-runner-set-only", false, "Make controller only reconcile AutoRunnerScaleSet object.") flag.BoolVar(&autoScalingRunnerSetOnly, "auto-scaling-runner-set-only", false, "Make controller only reconcile AutoRunnerScaleSet object.")
@@ -151,36 +152,101 @@ func main() {
ctrl.SetLogger(log) ctrl.SetLogger(log)
managerNamespace := ""
var newCache cache.NewCacheFunc
if autoScalingRunnerSetOnly { if autoScalingRunnerSetOnly {
// We don't support metrics for AutoRunnerScaleSet for now // We don't support metrics for AutoRunnerScaleSet for now
metricsAddr = "0" metricsAddr = "0"
managerNamespace = os.Getenv("CONTROLLER_MANAGER_POD_NAMESPACE")
if managerNamespace == "" {
log.Error(err, "unable to obtain manager pod namespace")
os.Exit(1)
}
if len(watchSingleNamespace) > 0 {
newCache = cache.MultiNamespacedCacheBuilder([]string{managerNamespace, watchSingleNamespace})
}
} }
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme, Scheme: scheme,
NewCache: newCache,
MetricsBindAddress: metricsAddr, MetricsBindAddress: metricsAddr,
LeaderElection: enableLeaderElection, LeaderElection: enableLeaderElection,
LeaderElectionID: leaderElectionId, LeaderElectionID: leaderElectionId,
Port: port, Port: port,
SyncPeriod: &syncPeriod, SyncPeriod: &syncPeriod,
Namespace: namespace, Namespace: namespace,
ClientDisableCacheFor: []client.Object{
&corev1.Secret{},
&corev1.ConfigMap{},
},
}) })
if err != nil { if err != nil {
log.Error(err, "unable to start manager") log.Error(err, "unable to start manager")
os.Exit(1) os.Exit(1)
} }
multiClient := actionssummerwindnet.NewMultiGitHubClient( if autoScalingRunnerSetOnly {
mgr.GetClient(), managerImage := os.Getenv("CONTROLLER_MANAGER_CONTAINER_IMAGE")
ghClient, if managerImage == "" {
) log.Error(err, "unable to obtain listener image")
os.Exit(1)
}
actionsMultiClient := actions.NewMultiClient( actionsMultiClient := actions.NewMultiClient(
"actions-runner-controller/"+build.Version, "actions-runner-controller/"+build.Version,
log.WithName("actions-clients"), log.WithName("actions-clients"),
) )
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Log: log.WithName("AutoscalingRunnerSet"),
Scheme: mgr.GetScheme(),
ControllerNamespace: managerNamespace,
DefaultRunnerScaleSetListenerImage: managerImage,
ActionsClient: actionsMultiClient,
DefaultRunnerScaleSetListenerImagePullSecrets: autoScalerImagePullSecrets,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "AutoscalingRunnerSet")
os.Exit(1)
}
if err = (&actionsgithubcom.EphemeralRunnerReconciler{
Client: mgr.GetClient(),
Log: log.WithName("EphemeralRunner"),
Scheme: mgr.GetScheme(),
ActionsClient: actionsMultiClient,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "EphemeralRunner")
os.Exit(1)
}
if err = (&actionsgithubcom.EphemeralRunnerSetReconciler{
Client: mgr.GetClient(),
Log: log.WithName("EphemeralRunnerSet"),
Scheme: mgr.GetScheme(),
ActionsClient: actionsMultiClient,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "EphemeralRunnerSet")
os.Exit(1)
}
if err = (&actionsgithubcom.AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Log: log.WithName("AutoscalingListener"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "AutoscalingListener")
os.Exit(1)
}
} else {
multiClient := actionssummerwindnet.NewMultiGitHubClient(
mgr.GetClient(),
ghClient,
)
if !autoScalingRunnerSetOnly {
runnerReconciler := &actionssummerwindnet.RunnerReconciler{ runnerReconciler := &actionssummerwindnet.RunnerReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: log.WithName("runner"), Log: log.WithName("runner"),
@@ -314,94 +380,15 @@ func main() {
log.Error(err, "unable to create webhook", "webhook", "RunnerReplicaSet") log.Error(err, "unable to create webhook", "webhook", "RunnerReplicaSet")
os.Exit(1) os.Exit(1)
} }
} injector := &actionssummerwindnet.PodRunnerTokenInjector{
} Client: mgr.GetClient(),
GitHubClient: multiClient,
// We use this environment avariable to turn on the ScaleSet related controllers. Log: ctrl.Log.WithName("webhook").WithName("PodRunnerTokenInjector"),
// Otherwise ARC's legacy chart is unable to deploy a working ARC controller-manager pod, }
// due to that the chart does not contain new actions.* CRDs while ARC requires those CRDs. if err = injector.SetupWithManager(mgr); err != nil {
// log.Error(err, "unable to create webhook server", "webhook", "PodRunnerTokenInjector")
// We might have used a more explicitly named environment variable for this, os.Exit(1)
// e.g. "CONTROLLER_MANAGER_ENABLE_SCALE_SET" to explicitly enable the new controllers,
// or "CONTROLLER_MANAGER_DISABLE_SCALE_SET" to explicitly disable the new controllers.
// However, doing so would affect either private ARC testers or current ARC users
// who run ARC without those variabls.
mgrPodName := os.Getenv("CONTROLLER_MANAGER_POD_NAME")
if mgrPodName != "" {
mgrPodNamespace := os.Getenv("CONTROLLER_MANAGER_POD_NAMESPACE")
var mgrPod corev1.Pod
err = mgr.GetAPIReader().Get(context.Background(), types.NamespacedName{Namespace: mgrPodNamespace, Name: mgrPodName}, &mgrPod)
if err != nil {
log.Error(err, fmt.Sprintf("unable to obtain manager pod: %s (%s)", mgrPodName, mgrPodNamespace))
os.Exit(1)
}
var mgrContainer *corev1.Container
for _, container := range mgrPod.Spec.Containers {
if container.Name == "manager" {
mgrContainer = &container
break
} }
}
if mgrContainer != nil {
log.Info("Detected manager container", "image", mgrContainer.Image)
} else {
log.Error(err, "unable to obtain manager container image")
os.Exit(1)
}
if err = (&actionsgithubcom.AutoscalingRunnerSetReconciler{
Client: mgr.GetClient(),
Log: log.WithName("AutoscalingRunnerSet"),
Scheme: mgr.GetScheme(),
ControllerNamespace: mgrPodNamespace,
DefaultRunnerScaleSetListenerImage: mgrContainer.Image,
ActionsClient: actionsMultiClient,
DefaultRunnerScaleSetListenerImagePullSecrets: autoScalerImagePullSecrets,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "AutoscalingRunnerSet")
os.Exit(1)
}
if err = (&actionsgithubcom.EphemeralRunnerReconciler{
Client: mgr.GetClient(),
Log: log.WithName("EphemeralRunner"),
Scheme: mgr.GetScheme(),
ActionsClient: actionsMultiClient,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "EphemeralRunner")
os.Exit(1)
}
if err = (&actionsgithubcom.EphemeralRunnerSetReconciler{
Client: mgr.GetClient(),
Log: log.WithName("EphemeralRunnerSet"),
Scheme: mgr.GetScheme(),
ActionsClient: actionsMultiClient,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "EphemeralRunnerSet")
os.Exit(1)
}
if err = (&actionsgithubcom.AutoscalingListenerReconciler{
Client: mgr.GetClient(),
Log: log.WithName("AutoscalingListener"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "AutoscalingListener")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
}
if !disableAdmissionWebhook && !autoScalingRunnerSetOnly {
injector := &actionssummerwindnet.PodRunnerTokenInjector{
Client: mgr.GetClient(),
GitHubClient: multiClient,
Log: ctrl.Log.WithName("webhook").WithName("PodRunnerTokenInjector"),
}
if err = injector.SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create webhook server", "webhook", "PodRunnerTokenInjector")
os.Exit(1)
} }
} }

View File

@@ -79,14 +79,34 @@ func (reader *EventReader) ProcessWorkflowJobEvent(ctx context.Context, event in
labels["repository_full_name"] = *n labels["repository_full_name"] = *n
keysAndValues = append(keysAndValues, "repository_full_name", *n) keysAndValues = append(keysAndValues, "repository_full_name", *n)
} }
if e.Repo.Owner != nil {
if l := e.Repo.Owner.Login; l != nil {
labels["owner"] = *l
keysAndValues = append(keysAndValues, "owner", *l)
}
}
} }
var org string
if e.Org != nil { if e.Org != nil {
if n := e.Org.Name; n != nil { if n := e.Org.Name; n != nil {
labels["organization"] = *e.Org.Name org = *n
keysAndValues = append(keysAndValues, "organization", *n) keysAndValues = append(keysAndValues, "organization", *n)
} }
} }
labels["organization"] = org
var wn string
if e.WorkflowJob != nil {
if n := e.WorkflowJob.WorkflowName; n != nil {
wn = *n
keysAndValues = append(keysAndValues, "workflow_name", *n)
}
}
labels["workflow_name"] = wn
log := reader.Log.WithValues(keysAndValues...)
// switch on job status // switch on job status
switch action := e.GetAction(); action { switch action := e.GetAction(); action {
@@ -102,14 +122,10 @@ func (reader *EventReader) ProcessWorkflowJobEvent(ctx context.Context, event in
parseResult, err := reader.fetchAndParseWorkflowJobLogs(ctx, e) parseResult, err := reader.fetchAndParseWorkflowJobLogs(ctx, e)
if err != nil { if err != nil {
reader.Log.Error(err, "reading workflow job log") log.Error(err, "reading workflow job log")
return return
} else { } else {
reader.Log.WithValues("job_name", *e.WorkflowJob.Name, "job_id", fmt.Sprint(*e.WorkflowJob.ID), "repository", *e.Repo.Name, "repository_full_name", *e.Repo.FullName) log.Info("reading workflow_job logs")
if len(*e.Org.Name) > 0 {
reader.Log.WithValues("organization", *e.Org.Name)
}
reader.Log.Info("reading workflow_job logs")
} }
githubWorkflowJobQueueDurationSeconds.With(labels).Observe(parseResult.QueueTime.Seconds()) githubWorkflowJobQueueDurationSeconds.With(labels).Observe(parseResult.QueueTime.Seconds())
@@ -122,10 +138,10 @@ func (reader *EventReader) ProcessWorkflowJobEvent(ctx context.Context, event in
parseResult, err := reader.fetchAndParseWorkflowJobLogs(ctx, e) parseResult, err := reader.fetchAndParseWorkflowJobLogs(ctx, e)
if err != nil { if err != nil {
reader.Log.Error(err, "reading workflow job log") log.Error(err, "reading workflow job log")
return return
} else { } else {
reader.Log.Info("reading workflow_job logs", keysAndValues...) log.Info("reading workflow_job logs", keysAndValues...)
} }
if *e.WorkflowJob.Conclusion == "failure" { if *e.WorkflowJob.Conclusion == "failure" {

View File

@@ -71,14 +71,19 @@ var (
} }
) )
func metricLabels(extras ...string) []string {
return append(append([]string{}, commonLabels...), extras...)
}
var ( var (
commonLabels = []string{"runs_on", "job_name", "organization", "repository", "repository_full_name", "owner", "workflow_name"}
githubWorkflowJobQueueDurationSeconds = prometheus.NewHistogramVec( githubWorkflowJobQueueDurationSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: "github_workflow_job_queue_duration_seconds", Name: "github_workflow_job_queue_duration_seconds",
Help: "Queue times for workflow jobs in seconds", Help: "Queue times for workflow jobs in seconds",
Buckets: runtimeBuckets, Buckets: runtimeBuckets,
}, },
[]string{"runs_on", "job_name"}, metricLabels(),
) )
githubWorkflowJobRunDurationSeconds = prometheus.NewHistogramVec( githubWorkflowJobRunDurationSeconds = prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
@@ -86,41 +91,41 @@ var (
Help: "Run times for workflow jobs in seconds", Help: "Run times for workflow jobs in seconds",
Buckets: runtimeBuckets, Buckets: runtimeBuckets,
}, },
[]string{"runs_on", "job_name", "job_conclusion"}, metricLabels("job_conclusion"),
) )
githubWorkflowJobConclusionsTotal = prometheus.NewCounterVec( githubWorkflowJobConclusionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "github_workflow_job_conclusions_total", Name: "github_workflow_job_conclusions_total",
Help: "Conclusions for tracked workflow jobs", Help: "Conclusions for tracked workflow jobs",
}, },
[]string{"runs_on", "job_name", "job_conclusion"}, metricLabels("job_conclusion"),
) )
githubWorkflowJobsQueuedTotal = prometheus.NewCounterVec( githubWorkflowJobsQueuedTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "github_workflow_jobs_queued_total", Name: "github_workflow_jobs_queued_total",
Help: "Total count of workflow jobs queued (events where job_status=queued)", Help: "Total count of workflow jobs queued (events where job_status=queued)",
}, },
[]string{"runs_on", "job_name"}, metricLabels(),
) )
githubWorkflowJobsStartedTotal = prometheus.NewCounterVec( githubWorkflowJobsStartedTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "github_workflow_jobs_started_total", Name: "github_workflow_jobs_started_total",
Help: "Total count of workflow jobs started (events where job_status=in_progress)", Help: "Total count of workflow jobs started (events where job_status=in_progress)",
}, },
[]string{"runs_on", "job_name"}, metricLabels(),
) )
githubWorkflowJobsCompletedTotal = prometheus.NewCounterVec( githubWorkflowJobsCompletedTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "github_workflow_jobs_completed_total", Name: "github_workflow_jobs_completed_total",
Help: "Total count of workflow jobs completed (events where job_status=completed)", Help: "Total count of workflow jobs completed (events where job_status=completed)",
}, },
[]string{"runs_on", "job_name"}, metricLabels(),
) )
githubWorkflowJobFailuresTotal = prometheus.NewCounterVec( githubWorkflowJobFailuresTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "github_workflow_job_failures_total", Name: "github_workflow_job_failures_total",
Help: "Conclusions for tracked workflow runs", Help: "Conclusions for tracked workflow runs",
}, },
[]string{"runs_on", "job_name", "failed_step", "exit_code"}, metricLabels("failed_step", "exit_code"),
) )
) )

View File

@@ -25,7 +25,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/go-logr/logr" "github.com/go-logr/logr"
gogithub "github.com/google/go-github/v47/github" gogithub "github.com/google/go-github/v50/github"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"github.com/actions/actions-runner-controller/github" "github.com/actions/actions-runner-controller/github"

View File

@@ -6,9 +6,9 @@ DIND_ROOTLESS_RUNNER_NAME ?= ${DOCKER_USER}/actions-runner-dind-rootless
OS_IMAGE ?= ubuntu-22.04 OS_IMAGE ?= ubuntu-22.04
TARGETPLATFORM ?= $(shell arch) TARGETPLATFORM ?= $(shell arch)
RUNNER_VERSION ?= 2.302.1 RUNNER_VERSION ?= 2.303.0
RUNNER_CONTAINER_HOOKS_VERSION ?= 0.2.0 RUNNER_CONTAINER_HOOKS_VERSION ?= 0.2.0
DOCKER_VERSION ?= 20.10.21 DOCKER_VERSION ?= 20.10.23
# default list of platforms for which multiarch image is built # default list of platforms for which multiarch image is built
ifeq (${PLATFORMS}, ) ifeq (${PLATFORMS}, )

View File

@@ -1 +1 @@
2.302.1 2.303.0

View File

@@ -5,7 +5,7 @@ ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
# Docker and Docker Compose arguments # Docker and Docker Compose arguments
ARG CHANNEL=stable ARG CHANNEL=stable
ARG DOCKER_VERSION=20.10.18 ARG DOCKER_VERSION=20.10.23
ARG DOCKER_COMPOSE_VERSION=v2.16.0 ARG DOCKER_COMPOSE_VERSION=v2.16.0
ARG DUMB_INIT_VERSION=1.2.5 ARG DUMB_INIT_VERSION=1.2.5

View File

@@ -5,7 +5,7 @@ ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
# Docker and Docker Compose arguments # Docker and Docker Compose arguments
ARG CHANNEL=stable ARG CHANNEL=stable
ARG DOCKER_VERSION=20.10.21 ARG DOCKER_VERSION=20.10.23
ARG DOCKER_COMPOSE_VERSION=v2.16.0 ARG DOCKER_COMPOSE_VERSION=v2.16.0
ARG DUMB_INIT_VERSION=1.2.5 ARG DUMB_INIT_VERSION=1.2.5
ARG RUNNER_USER_UID=1001 ARG RUNNER_USER_UID=1001

View File

@@ -5,7 +5,7 @@ ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
# Docker and Docker Compose arguments # Docker and Docker Compose arguments
ARG CHANNEL=stable ARG CHANNEL=stable
ARG DOCKER_VERSION=20.10.18 ARG DOCKER_VERSION=20.10.23
ARG DOCKER_COMPOSE_VERSION=v2.16.0 ARG DOCKER_COMPOSE_VERSION=v2.16.0
ARG DUMB_INIT_VERSION=1.2.5 ARG DUMB_INIT_VERSION=1.2.5

View File

@@ -5,7 +5,7 @@ ARG RUNNER_VERSION
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0 ARG RUNNER_CONTAINER_HOOKS_VERSION=0.2.0
# Docker and Docker Compose arguments # Docker and Docker Compose arguments
ARG CHANNEL=stable ARG CHANNEL=stable
ARG DOCKER_VERSION=20.10.21 ARG DOCKER_VERSION=20.10.23
ARG DOCKER_COMPOSE_VERSION=v2.16.0 ARG DOCKER_COMPOSE_VERSION=v2.16.0
ARG DUMB_INIT_VERSION=1.2.5 ARG DUMB_INIT_VERSION=1.2.5
ARG RUNNER_USER_UID=1001 ARG RUNNER_USER_UID=1001

View File

@@ -41,7 +41,7 @@ var (
testResultCMNamePrefix = "test-result-" testResultCMNamePrefix = "test-result-"
RunnerVersion = "2.302.1" RunnerVersion = "2.303.0"
) )
// If you're willing to run this test via VS Code "run test" or "debug test", // If you're willing to run this test via VS Code "run test" or "debug test",

View File

@@ -11,6 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
@@ -92,17 +93,24 @@ func TestARCJobs(t *testing.T) {
) )
t.Run("Get available pods during job run", func(t *testing.T) { t.Run("Get available pods during job run", func(t *testing.T) {
c := http.Client{} c := http.Client{}
dateTime := os.Getenv("DATE_TIME") targetArcName := os.Getenv("ARC_NAME")
require.NotEmpty(t, targetArcName, "ARC_NAME environment variable is required for this test to run. (e.g. arc-e2e-test)")
targetWorkflow := os.Getenv("WORKFLOW_FILE")
require.NotEmpty(t, targetWorkflow, "WORKFLOW_FILE environment variable is required for this test to run. (e.g. e2e_test.yml)")
ght := os.Getenv("GITHUB_TOKEN")
require.NotEmpty(t, ght, "GITHUB_TOKEN environment variable is required for this test to run.")
// We are triggering manually a workflow that already exists in the repo. // We are triggering manually a workflow that already exists in the repo.
// This workflow is expected to spin up a number of runner pods matching the runners value set in podCountsByType. // This workflow is expected to spin up a number of runner pods matching the runners value set in podCountsByType.
url := "https://api.github.com/repos/actions-runner-controller/arc_e2e_test_dummy/actions/workflows/e2e-test-dispatch-workflow.yaml/dispatches" url := "https://api.github.com/repos/actions-runner-controller/arc_e2e_test_dummy/actions/workflows/" + targetWorkflow + "/dispatches"
jsonStr := []byte(fmt.Sprintf(`{"ref":"main", "inputs":{"date_time":"%s"}}`, dateTime)) jsonStr := []byte(fmt.Sprintf(`{"ref":"main", "inputs":{"arc_name":"%s"}}`, targetArcName))
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr)) req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ght := os.Getenv("GITHUB_TOKEN")
req.Header.Add("Accept", "application/vnd.github+json") req.Header.Add("Accept", "application/vnd.github+json")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ght)) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ght))
req.Header.Add("X-GitHub-Api-Version", "2022-11-28") req.Header.Add("X-GitHub-Api-Version", "2022-11-28")