Compare commits

..

6 Commits

Author SHA1 Message Date
Nikola Jokic
0824407642 wip but requires a lot of refactoring 2026-01-30 17:54:01 +01:00
Nikola Jokic
14c9793b0b slowly migrating v2 tests 2026-01-29 20:20:44 +01:00
Nikola Jokic
87c91832e2 helm unittest 2026-01-27 20:05:37 +01:00
Nikola Jokic
79b2ff087c Taking a stab at adding native extension 2026-01-27 18:16:16 +01:00
Nikola Jokic
506abf167d Helper renames 2026-01-27 11:53:34 +01:00
Nikola Jokic
a1a796ee4d rewriting the gha-runner-scale-set-chart 2026-01-26 20:11:58 +01:00
116 changed files with 49122 additions and 62 deletions

View File

@@ -18,7 +18,7 @@ on:
workflow_dispatch:
env:
KUBE_SCORE_VERSION: 1.16.1
HELM_VERSION: v3.17.0
HELM_VERSION: v3.19.4
permissions:
contents: read
@@ -61,6 +61,18 @@ jobs:
if [[ -n "$changed" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi
echo "changed_charts<<EOF" >> $GITHUB_OUTPUT
echo "$changed" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Install helm-unittest
if: contains(steps.list-changed.outputs.changed_charts, 'charts/gha-runner-scale-set-controller-experimental')
run: |
helm plugin install https://github.com/helm-unittest/helm-unittest.git
- name: Run helm-unittest (gha-runner-scale-set-controller-experimental)
if: contains(steps.list-changed.outputs.changed_charts, 'charts/gha-runner-scale-set-controller-experimental')
run: |
helm unittest ./charts/gha-runner-scale-set-controller-experimental/
- name: Run chart-testing (lint)
run: |

View File

@@ -182,6 +182,10 @@ chart-crds:
cp config/crd/bases/actions.github.com_autoscalinglisteners.yaml charts/gha-runner-scale-set-controller/crds/
cp config/crd/bases/actions.github.com_ephemeralrunnersets.yaml charts/gha-runner-scale-set-controller/crds/
cp config/crd/bases/actions.github.com_ephemeralrunners.yaml charts/gha-runner-scale-set-controller/crds/
cp config/crd/bases/actions.github.com_autoscalingrunnersets.yaml charts/gha-runner-scale-set-controller-experimental/crds/
cp config/crd/bases/actions.github.com_autoscalinglisteners.yaml charts/gha-runner-scale-set-controller-experimental/crds/
cp config/crd/bases/actions.github.com_ephemeralrunnersets.yaml charts/gha-runner-scale-set-controller-experimental/crds/
cp config/crd/bases/actions.github.com_ephemeralrunners.yaml charts/gha-runner-scale-set-controller-experimental/crds/
rm charts/actions-runner-controller/crds/actions.github.com_autoscalingrunnersets.yaml
rm charts/actions-runner-controller/crds/actions.github.com_autoscalinglisteners.yaml
rm charts/actions-runner-controller/crds/actions.github.com_ephemeralrunnersets.yaml

View File

@@ -0,0 +1,24 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
tests/

View File

@@ -0,0 +1,33 @@
apiVersion: v2
name: gha-runner-scale-set-controller-experimental
description: A Helm chart for install actions-runner-controller CRD
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.14.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.14.0"
home: https://github.com/actions/actions-runner-controller
sources:
- "https://github.com/actions/actions-runner-controller"
maintainers:
- name: actions
url: https://github.com/actions

View File

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

View File

@@ -0,0 +1,3 @@
Thank you for installing {{ .Chart.Name }}.
Your release is named {{ .Release.Name }}.

View File

@@ -0,0 +1,67 @@
{{/*
Allow overriding the namespace for the resources.
*/}}
{{- define "gha-controller.namespace" -}}
{{- if .Values.namespaceOverride }}
{{- .Values.namespaceOverride }}
{{- else }}
{{- .Release.Namespace }}
{{- end }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "gha-controller.name" -}}
{{- if .Values.nameOverride }}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default (include "gha-base-name" .) .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Labels applied to the controller deployment
*/}}
{{- define "gha-controller.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "controller-manager" -}}
{{- $commonLabels := include "gha-common.labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.controller.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.labels | default (dict)) | fromYaml -}}
{{- $labels := mergeOverwrite $global $userLabels $resourceLabels $commonLabels -}}
{{- /* Reserved actions.github.com/* labels owned by the chart itself */ -}}
{{- $_ := set $labels "actions.github.com/controller-service-account-namespace" (include "gha-controller.namespace" .) -}}
{{- $_ := set $labels "actions.github.com/controller-service-account-name" (include "gha-controller.service-account-name" .) -}}
{{- with .Values.controller.manager.config.watchSingleNamespace }}
{{- $_ := set $labels "actions.github.com/controller-watch-single-namespace" . -}}
{{- end }}
{{- toYaml $labels -}}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "gha-controller.service-account-name" -}}
{{- if eq .Values.controller.serviceAccount.name "default"}}
{{- fail "serviceAccount.name cannot be set to 'default'" }}
{{- end }}
{{- if .Values.controller.serviceAccount.create }}
{{- default (include "gha-controller.name" .) .Values.controller.serviceAccount.name }}
{{- else }}
{{- if not .Values.controller.serviceAccount.name }}
{{- fail "serviceAccount.name must be set if serviceAccount.create is false" }}
{{- else }}
{{- .Values.controller.serviceAccount.name }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,126 @@
{{/*
Labels applied to the controller Pod template (spec.template.metadata.labels)
*/}}
{{- define "gha-controller-template.labels" -}}
{{- $static := dict "app.kubernetes.io/part-of" "gha-rs-controller" "app.kubernetes.io/component" "controller-manager" -}}
{{- $_ := set $static "app.kubernetes.io/version" (.Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-") -}}
{{- $selector := include "gha-controller.selector-labels" . | fromYaml -}}
{{- $podUser := include "apply-non-reserved-gha-labels-and-annotations" (.Values.controller.pod.metadata.labels | default (dict)) | fromYaml -}}
{{- $labels := mergeOverwrite $podUser $selector $static -}}
{{- toYaml $labels -}}
{{- end }}
{{/*
Annotations applied to the controller Pod template (spec.template.metadata.annotations)
*/}}
{{- define "gha-controller-template.annotations" -}}
{{- $static := dict "kubectl.kubernetes.io/default-container" "manager" -}}
{{- $podUser := include "apply-non-reserved-gha-labels-and-annotations" (.Values.controller.pod.metadata.annotations | default (dict)) | fromYaml -}}
{{- $annotations := mergeOverwrite $podUser $static -}}
{{- toYaml $annotations -}}
{{- end }}
{{- define "gha-controller-template.manager-container" -}}
name: manager
image: "{{ .Values.controller.manager.container.image }}"
imagePullPolicy: {{ default .Values.controller.manager.container.pullPolicy "IfNotPresent" }}
command:
- "/manager"
args:
- "--auto-scaling-runner-set-only"
{{- if gt (int (default 1 .Values.controller.replicaCount)) 1 }}
- "--enable-leader-election"
- "--leader-election-id={{ include "gha-controller.name" . }}"
{{- end }}
{{- with .Values.imagePullSecrets }}
{{- range . }}
- "--auto-scaler-image-pull-secrets={{- .name -}}"
{{- end }}
{{- end }}
{{- with .Values.controller.manager.config.logLevel }}
- "--log-level={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.logFormat }}
- "--log-format={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.watchSingleNamespace }}
- "--watch-single-namespace={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.runnerMaxConcurrentReconciles }}
- "--runner-max-concurrent-reconciles={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.updateStrategy }}
- "--update-strategy={{ . }}"
{{- end }}
{{- if .Values.controller.metrics }}
{{- with .Values.controller.metrics }}
- "--listener-metrics-addr={{ .listenerAddr }}"
- "--listener-metrics-endpoint={{ .listenerEndpoint }}"
- "--metrics-addr={{ .controllerManagerAddr }}"
{{- end }}
{{- else }}
- "--listener-metrics-addr=0"
- "--listener-metrics-endpoint="
- "--metrics-addr=0"
{{- end }}
{{- range .Values.controller.manager.config.excludeLabelPropagationPrefixes }}
- "--exclude-label-propagation-prefix={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.k8sClientRateLimiterQPS }}
- "--k8s-client-rate-limiter-qps={{ . }}"
{{- end }}
{{- with .Values.controller.manager.config.k8sClientRateLimiterBurst }}
- "--k8s-client-rate-limiter-burst={{ . }}"
{{- end }}
{{- with .Values.controller.manager.container.extraArgs }}
{{- range . }}
- "{{ . }}"
{{- end }}
{{- end }}
{{- $ports := list -}}
{{- if .Values.controller.metrics }}
{{- $metricsPort := dict "containerPort" ((regexReplaceAll ":([0-9]+)" .Values.controller.metrics.controllerManagerAddr "${1}") | int) "protocol" "TCP" "name" "metrics" -}}
{{- $ports = append $ports $metricsPort -}}
{{- end }}
{{- with .Values.controller.manager.container.extraPorts }}
{{- if kindIs "slice" . }}
{{- $ports = concat $ports . -}}
{{- end }}
{{- end }}
{{- if gt (len $ports) 0 }}
ports:
{{- toYaml $ports | nindent 2 }}
{{- end }}
env:
- name: CONTROLLER_MANAGER_CONTAINER_IMAGE
value: "{{ .Values.controller.manager.container.image }}"
- name: CONTROLLER_MANAGER_POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- with .Values.controller.manager.container.env }}
{{- if kindIs "slice" . }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}
{{- with .Values.controller.manager.container.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with .Values.controller.manager.container.securityContext }}
securityContext:
{{- toYaml . | nindent 2 }}
{{- end }}
volumeMounts:
- mountPath: /tmp
name: tmp
{{- with .Values.controller.manager.container.extraVolumeMounts }}
{{- range . }}
- {{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- range .Values.controller.pod.extraVolumeMounts }}
- {{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,74 @@
{{- define "gha-base-name" -}}
gha-rs-controller
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "gha-controller.chart" -}}
{{- printf "%s-%s" (include "gha-base-name" .) .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "gha-common.labels" -}}
helm.sh/chart: {{ include "gha-controller.chart" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/part-of: "gha-rs-controller"
app.kubernetes.io/managed-by: {{ .Release.Service | quote }}
app.kubernetes.io/name: {{ include "gha-controller.name" . }}
app.kubernetes.io/namespace: {{ include "gha-controller.namespace" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "gha-controller.manager-cluster-role-name" -}}
{{- include "gha-controller.name" . }}
{{- end }}
{{- define "gha-controller.manager-cluster-role-binding" -}}
{{- include "gha-controller.name" . }}
{{- end }}
{{- define "gha-controller.manager-single-namespace-role-name" -}}
{{- include "gha-controller.name" . }}-single-namespace
{{- end }}
{{- define "gha-controller.manager-single-namespace-role-binding" -}}
{{- include "gha-controller.name" . }}-single-namespace
{{- end }}
{{- define "gha-controller.manager-single-namespace-watch-role-name" -}}
{{- include "gha-controller.name" . }}-single-namespace-watch
{{- end }}
{{- define "gha-controller.manager-single-namespace-watch-role-binding" -}}
{{- include "gha-controller.name" . }}-single-namespace-watch
{{- end }}
{{- define "gha-controller.manager-listener-role-name" -}}
{{- include "gha-controller.name" . }}-listener
{{- end }}
{{- define "gha-controller.manager-listener-role-binding" -}}
{{- include "gha-controller.name" . }}-listener
{{- end }}
{{- define "gha-controller.leaderElectionRoleName" -}}
{{- include "gha-controller.name" . }}-leader-election
{{- end }}
{{- define "gha-controller.leader-election-role-name" -}}
{{- include "gha-controller.leaderElectionRoleName" . -}}
{{- end }}
{{- define "gha-controller.leaderElectionRoleBinding" -}}
{{- include "gha-controller.name" . }}-leader-election
{{- end }}
{{- define "gha-controller.leader-election-role-binding" -}}
{{- include "gha-controller.leaderElectionRoleBinding" . -}}
{{- end }}

View File

@@ -0,0 +1,25 @@
{{/*
Takes a map of user labels and removes the ones with "actions.github.com/" prefix
*/}}
{{- define "apply-non-reserved-gha-labels-and-annotations" -}}
{{- $userLabels := . -}}
{{- $processed := dict -}}
{{- range $key, $value := $userLabels -}}
{{- if not (hasPrefix "actions.github.com/" $key) -}}
{{- $_ := set $processed $key $value -}}
{{- end -}}
{{- end -}}
{{- if not (empty $processed) -}}
{{- $processed | toYaml }}
{{- end }}
{{- end }}
{{- define "gha-controller.selector-labels" -}}
app.kubernetes.io/name: {{ include "gha-controller.name" . }}
app.kubernetes.io/namespace: {{ include "gha-controller.namespace" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "gha-controller.labels" -}}
{{- include "gha-controller.labels" . -}}
{{- end }}

View File

@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "gha-controller.name" . }}
namespace: {{ include "gha-controller.namespace" . }}
labels:
{{- include "gha-controller.labels" . | nindent 4 }}
spec:
replicas: {{ default 1 .Values.controller.replicaCount }}
selector:
matchLabels:
{{- include "gha-controller.selector-labels" . | nindent 6 }}
template:
metadata:
annotations:
{{- include "gha-controller-template.annotations" . | nindent 8 }}
labels:
{{- include "gha-controller-template.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "gha-controller.service-account-name" . }}
containers:
-
{{- include "gha-controller-template.manager-container" . | nindent 10 }}
{{- range .Values.controller.pod.containers }}
-
{{- toYaml . | nindent 10 }}
{{- end }}
terminationGracePeriodSeconds: {{ default 10 .Values.controller.pod.terminationGracePeriodSeconds }}
volumes:
- name: tmp
emptyDir: {}
{{- range .Values.controller.pod.extraVolumes }}
- {{- toYaml . | nindent 10 }}
{{- end }}
{{- $runnerPodSpecExtraFields := (omit .Values.controller.pod "containers" "serviceAccountName" "terminationGracePeriodSeconds") -}}
{{- if gt (len $runnerPodSpecExtraFields) 0 }}
{{- toYaml $runnerPodSpecExtraFields | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if gt (int (default 1 .Values.controller.replicaCount)) 1 }}
# permissions to do leader election.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-controller.leader-election-role-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
rules:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "watch", "list", "delete", "update", "create"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if gt (int (default 1 .Values.controller.replicaCount)) 1 }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-controller.leader-election-role-binding" . }}
namespace: {{ include "gha-controller.namespace" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-controller.leader-election-role-name" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
{{- end }}

View File

@@ -0,0 +1,144 @@
{{- if empty .Values.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "gha-controller.manager-cluster-role-name" . }}
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:
- 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:
- 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:
- ephemeralrunnersets/finalizers
verbs:
- 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:
- ""
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
- patch
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if empty .Values.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "gha-controller.manager-cluster-role-binding" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "gha-controller.manager-cluster-role-name" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
{{- end }}

View File

@@ -0,0 +1,40 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-controller.manager-listener-role-name" . }}
namespace: {{ include "gha-controller.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

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-controller.manager-listener-role-binding" . }}
namespace: {{ include "gha-controller.namespace" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-controller.manager-listener-role-name" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}

View File

@@ -0,0 +1,84 @@
{{- if .Values.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-controller.manager-single-namespace-role-name" . }}
namespace: {{ include "gha-controller.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.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-controller.manager-single-namespace-role-binding" . }}
namespace: {{ include "gha-controller.namespace" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-controller.manager-single-namespace-role-name" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
{{- end }}

View File

@@ -0,0 +1,125 @@
{{- if .Values.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "gha-controller.manager-single-namespace-watch-role-name" . }}
namespace: {{ .Values.controller.manager.config.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:
- ephemeralrunnersets/finalizers
verbs:
- 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
- patch
{{- end }}

View File

@@ -0,0 +1,15 @@
{{- if .Values.controller.manager.config.watchSingleNamespace }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "gha-controller.manager-single-namespace-watch-role-binding" . }}
namespace: {{ .Values.controller.manager.config.watchSingleNamespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "gha-controller.manager-single-namespace-watch-role-name" . }}
subjects:
- kind: ServiceAccount
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
{{- end }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.controller.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "gha-controller.service-account-name" . }}
namespace: {{ include "gha-controller.namespace" . }}
labels:
{{- include "gha-controller.labels" . | nindent 4 }}
{{- with .Values.controller.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,75 @@
suite: "Controller Deployment args"
templates:
- deployment.yaml
tests:
- it: should include metrics-disabled flags by default
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--metrics-addr=0"
- contains:
path: spec.template.spec.containers[0].args
content: "--listener-metrics-addr=0"
- contains:
path: spec.template.spec.containers[0].args
content: "--listener-metrics-endpoint="
- it: should include watch-single-namespace flag when configured
set:
controller:
manager:
config:
watchSingleNamespace: "demo"
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--watch-single-namespace=demo"
- it: should include exclude-label-propagation-prefix flags when configured
set:
controller:
manager:
config:
excludeLabelPropagationPrefixes:
- "prefix.com/"
- "complete.io/label"
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--exclude-label-propagation-prefix=prefix.com/"
- contains:
path: spec.template.spec.containers[0].args
content: "--exclude-label-propagation-prefix=complete.io/label"
- it: should render metrics port when metrics are enabled
set:
controller:
metrics:
controllerManagerAddr: ":8080"
listenerAddr: ":8081"
listenerEndpoint: "/metrics"
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- equal:
path: spec.template.spec.containers[0].ports[0].containerPort
value: 8080
- contains:
path: spec.template.spec.containers[0].args
content: "--metrics-addr=:8080"
- contains:
path: spec.template.spec.containers[0].args
content: "--listener-metrics-addr=:8081"
- contains:
path: spec.template.spec.containers[0].args
content: "--listener-metrics-endpoint=/metrics"

View File

@@ -0,0 +1,46 @@
suite: "Controller Deployment env"
templates:
- deployment.yaml
tests:
- it: should not render envFrom in manager container
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.template.spec.containers[0].envFrom
- notExists:
path: spec.template.spec.containers[0].ports
- it: should include extra env entries from values
set:
controller:
manager:
container:
env:
- name: "FOO"
value: "bar"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: "FOO"
value: "bar"
- it: should enable leader election when replicaCount > 1
set:
controller:
replicaCount: 2
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--enable-leader-election"
- contains:
path: spec.template.spec.containers[0].args
content: "--leader-election-id=test-name-gha-rs-controller"

View File

@@ -0,0 +1,54 @@
suite: "Controller Deployment extra containers"
templates:
- deployment.yaml
tests:
- it: should render manager container first and then extra containers
set:
controller:
pod:
containers:
- name: "sidecar"
image: "busybox:1.36"
command:
- "sh"
- "-c"
args:
- "echo hello && sleep 3600"
- name: "another"
image: "alpine:3.19"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].name
value: "manager"
- equal:
path: spec.template.spec.containers[1].name
value: "sidecar"
- equal:
path: spec.template.spec.containers[1].image
value: "busybox:1.36"
- equal:
path: spec.template.spec.containers[1].command[0]
value: "sh"
- equal:
path: spec.template.spec.containers[1].args[0]
value: "echo hello && sleep 3600"
- equal:
path: spec.template.spec.containers[2].name
value: "another"
- equal:
path: spec.template.spec.containers[2].image
value: "alpine:3.19"
- it: should not fail when extra containers are unset
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].name
value: "manager"
- notExists:
path: spec.template.spec.containers[1]

View File

@@ -0,0 +1,33 @@
suite: "Controller Deployment imagePullSecrets"
templates:
- deployment.yaml
tests:
- it: should not render imagePullSecrets by default
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.template.spec.imagePullSecrets
- it: should render imagePullSecrets and forward them as args when configured
set:
imagePullSecrets:
- name: regcred
- name: another
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.imagePullSecrets[0].name
value: regcred
- equal:
path: spec.template.spec.imagePullSecrets[1].name
value: another
- contains:
path: spec.template.spec.containers[0].args
content: "--auto-scaler-image-pull-secrets=regcred"
- contains:
path: spec.template.spec.containers[0].args
content: "--auto-scaler-image-pull-secrets=another"

View File

@@ -0,0 +1,52 @@
suite: "Controller Deployment pod extra fields"
templates:
- deployment.yaml
tests:
- it: should render extra pod spec fields from controller.pod
set:
controller:
pod:
nodeSelector:
kubernetes.io/os: linux
tolerations:
- key: "dedicated"
operator: "Equal"
value: "arc"
effect: "NoSchedule"
hostNetwork: true
dnsPolicy: "ClusterFirstWithHostNet"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.nodeSelector["kubernetes.io/os"]
value: "linux"
- equal:
path: spec.template.spec.tolerations[0].key
value: "dedicated"
- equal:
path: spec.template.spec.tolerations[0].value
value: "arc"
- equal:
path: spec.template.spec.hostNetwork
value: true
- equal:
path: spec.template.spec.dnsPolicy
value: "ClusterFirstWithHostNet"
- it: should not allow overriding serviceAccountName via controller.pod
set:
controller:
pod:
serviceAccountName: "hacker-sa"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.serviceAccountName
value: "test-name-gha-rs-controller"
- notEqual:
path: spec.template.spec.serviceAccountName
value: "hacker-sa"

View File

@@ -0,0 +1,27 @@
suite: "Controller Deployment smoke"
templates:
- deployment.yaml
tests:
- it: should render deployment basics
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: apiVersion
value: "apps/v1"
- equal:
path: kind
value: "Deployment"
- equal:
path: metadata.name
value: "test-name-gha-rs-controller"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: spec.template.spec.containers[0].name
value: "manager"
- contains:
path: spec.template.spec.containers[0].command
content: "/manager"

View File

@@ -0,0 +1,37 @@
suite: "Controller Manager ClusterRoleBinding"
templates:
- manager_cluster_role_binding.yaml
tests:
- it: should render when watchSingleNamespace is empty
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: apiVersion
value: "rbac.authorization.k8s.io/v1"
- equal:
path: kind
value: "ClusterRoleBinding"
- equal:
path: subjects[0].kind
value: "ServiceAccount"
- equal:
path: subjects[0].name
value: "test-name-gha-rs-controller"
- equal:
path: subjects[0].namespace
value: "test-namespace"
- it: should not render when watchSingleNamespace is set
set:
controller:
manager:
config:
watchSingleNamespace: "my-ns"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,24 @@
suite: "Controller namespaceOverride"
templates:
- deployment.yaml
- serviceaccount.yaml
tests:
- it: should apply namespaceOverride to deployment and serviceaccount
set:
namespaceOverride: "override-ns"
release:
name: "test-name"
namespace: "release-ns"
asserts:
- equal:
path: metadata.namespace
value: "override-ns"
template: deployment.yaml
- equal:
path: metadata.labels["actions.github.com/controller-service-account-namespace"]
value: "override-ns"
template: deployment.yaml
- equal:
path: metadata.namespace
value: "override-ns"
template: serviceaccount.yaml

View File

@@ -0,0 +1,38 @@
suite: "Controller RBAC cluster"
templates:
- manager_cluster_role.yaml
tests:
- it: should render manager ClusterRole when watchSingleNamespace is empty
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- equal:
path: kind
value: "ClusterRole"
- equal:
path: metadata.name
value: "test-arc-gha-rs-controller"
- contains:
path: rules
content:
apiGroups:
- ""
resources:
- pods
verbs:
- list
- watch
- it: should not render manager ClusterRole when watchSingleNamespace is set
set:
controller:
manager:
config:
watchSingleNamespace: "demo"
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,52 @@
suite: "Controller RBAC leader election"
templates:
- leader_election_role.yaml
- leader_election_role_binding.yaml
tests:
- it: should not render leader election resources when replicaCount is 1
set:
controller:
replicaCount: 1
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- hasDocuments:
count: 0
template: leader_election_role.yaml
- hasDocuments:
count: 0
template: leader_election_role_binding.yaml
- it: should render leader election resources when replicaCount > 1
set:
controller:
replicaCount: 2
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- equal:
path: kind
value: "Role"
template: leader_election_role.yaml
- equal:
path: metadata.name
value: "test-arc-gha-rs-controller-leader-election"
template: leader_election_role.yaml
- equal:
path: metadata.namespace
value: "test-ns"
template: leader_election_role.yaml
- equal:
path: kind
value: "RoleBinding"
template: leader_election_role_binding.yaml
- equal:
path: roleRef.name
value: "test-arc-gha-rs-controller-leader-election"
template: leader_election_role_binding.yaml
- equal:
path: subjects[0].name
value: "test-arc-gha-rs-controller"
template: leader_election_role_binding.yaml

View File

@@ -0,0 +1,68 @@
suite: "Controller RBAC listener"
templates:
- manager_listener_role.yaml
- manager_listener_role_binding.yaml
tests:
- it: should render listener role with expected rules
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- equal:
path: kind
value: "Role"
template: manager_listener_role.yaml
- equal:
path: metadata.name
value: "test-arc-gha-rs-controller-listener"
template: manager_listener_role.yaml
- equal:
path: metadata.namespace
value: "test-ns"
template: manager_listener_role.yaml
- equal:
path: rules[0].resources[0]
value: "pods"
template: manager_listener_role.yaml
- equal:
path: rules[1].resources[0]
value: "pods/status"
template: manager_listener_role.yaml
- equal:
path: rules[2].resources[0]
value: "secrets"
template: manager_listener_role.yaml
- equal:
path: rules[3].resources[0]
value: "serviceaccounts"
template: manager_listener_role.yaml
- it: should bind listener role to controller serviceaccount
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- equal:
path: kind
value: "RoleBinding"
template: manager_listener_role_binding.yaml
- equal:
path: metadata.name
value: "test-arc-gha-rs-controller-listener"
template: manager_listener_role_binding.yaml
- equal:
path: metadata.namespace
value: "test-ns"
template: manager_listener_role_binding.yaml
- equal:
path: roleRef.name
value: "test-arc-gha-rs-controller-listener"
template: manager_listener_role_binding.yaml
- equal:
path: subjects[0].name
value: "test-arc-gha-rs-controller"
template: manager_listener_role_binding.yaml
- equal:
path: subjects[0].namespace
value: "test-ns"
template: manager_listener_role_binding.yaml

View File

@@ -0,0 +1,56 @@
suite: "Controller RBAC single-namespace mode"
templates:
- manager_single_namespace_controller_role.yaml
- manager_single_namespace_controller_role_binding.yaml
- manager_single_namespace_watch_role.yaml
- manager_single_namespace_watch_role_binding.yaml
tests:
- it: should not render single-namespace roles when watchSingleNamespace is empty
set:
controller:
manager:
config:
watchSingleNamespace: ""
release:
name: "test-arc"
namespace: "test-ns"
asserts:
- hasDocuments:
count: 0
template: manager_single_namespace_controller_role.yaml
- hasDocuments:
count: 0
template: manager_single_namespace_controller_role_binding.yaml
- hasDocuments:
count: 0
template: manager_single_namespace_watch_role.yaml
- hasDocuments:
count: 0
template: manager_single_namespace_watch_role_binding.yaml
- it: should render roles in controller namespace and watch namespace
set:
controller:
manager:
config:
watchSingleNamespace: "demo"
release:
name: "test-arc"
namespace: "ctrl-ns"
asserts:
- equal:
path: metadata.namespace
value: "ctrl-ns"
template: manager_single_namespace_controller_role.yaml
- equal:
path: metadata.namespace
value: "ctrl-ns"
template: manager_single_namespace_controller_role_binding.yaml
- equal:
path: metadata.namespace
value: "demo"
template: manager_single_namespace_watch_role.yaml
- equal:
path: metadata.namespace
value: "demo"
template: manager_single_namespace_watch_role_binding.yaml

View File

@@ -0,0 +1,72 @@
suite: "Controller ServiceAccount"
templates:
- serviceaccount.yaml
tests:
- it: should render serviceaccount by default
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: apiVersion
value: "v1"
- equal:
path: kind
value: "ServiceAccount"
- equal:
path: metadata.name
value: "test-name-gha-rs-controller"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: metadata.labels["actions.github.com/controller-service-account-name"]
value: "test-name-gha-rs-controller"
- equal:
path: metadata.labels["actions.github.com/controller-service-account-namespace"]
value: "test-namespace"
- it: should allow overriding serviceAccount.name when create is true
set:
controller:
serviceAccount:
create: true
name: "overwritten-name"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.name
value: "overwritten-name"
- equal:
path: metadata.labels["actions.github.com/controller-service-account-name"]
value: "overwritten-name"
- it: should render serviceAccount annotations
set:
controller:
serviceAccount:
create: true
annotations:
foo: bar
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations.foo
value: "bar"
- it: should not render when serviceAccount.create is false
set:
controller:
serviceAccount:
create: false
name: "existing-sa"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,32 @@
suite: "Controller ServiceAccount validation"
templates:
- serviceaccount.yaml
- deployment.yaml
tests:
- it: should fail if serviceAccount.name is 'default'
set:
controller:
serviceAccount:
create: true
name: "default"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: "serviceAccount.name cannot be set to 'default'"
template: serviceaccount.yaml
- it: should fail when serviceAccount.create is false and name is not set
set:
controller:
serviceAccount:
create: false
name: ""
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: "serviceAccount.name must be set if serviceAccount.create is false"
template: deployment.yaml

View File

@@ -0,0 +1,111 @@
# Global chart-level labels applied to all resources (Deployment, RBAC, etc.).
labels: {}
# Overrides the default `.Release.Namespace` for all resources in this chart.
namespaceOverride: ""
# Optional imagePullSecrets added to the controller Pod spec.
# When set, the manager container also receives `--auto-scaler-image-pull-secrets=<name>` args.
imagePullSecrets: []
controller:
# Number of controller replicas.
replicaCount: 1
# Deployment-level metadata
metadata:
labels: {}
annotations: {}
manager:
config:
# Log level: "debug", "info", "warn", "error".
logLevel: "debug"
# Log format: "text", "json".
logFormat: "text"
# Restricts the controller to only watch resources in the desired namespace.
# Defaults to watch all namespaces when unset.
watchSingleNamespace: ""
# The maximum number of concurrent reconciles which can be run by the EphemeralRunner controller.
runnerMaxConcurrentReconciles: 2
# How the controller handles upgrades with running jobs: "immediate" or "eventual".
updateStrategy: "immediate"
# List of label prefixes that should NOT be propagated to internal resources.
excludeLabelPropagationPrefixes: []
# Example:
# excludeLabelPropagationPrefixes:
# - "argocd.argoproj.io/instance"
# K8s client rate limiter parameters.
k8sClientRateLimiterQPS: null
k8sClientRateLimiterBurst: null
container:
image: "ghcr.io/actions/gha-runner-scale-set-controller:latest"
pullPolicy: IfNotPresent
# Extra arguments appended to the default set generated by the chart.
extraArgs: []
# Container-level environment variables.
env: []
# Container-level security context.
securityContext: {}
# Container-level resource requests/limits.
resources: {}
# Additional volume mounts on the manager container.
extraVolumeMounts: []
# Extra container ports (metrics port is derived from controller.metrics).
extraPorts: []
serviceAccount:
# Specifies whether a service account should be created.
create: true
# Annotations to add to the service account.
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template.
name: ""
# Pod-level configuration.
pod:
metadata:
labels: {}
annotations: {}
spec: {}
# Pod-level security context.
securityContext: {}
# Pod priority class name.
priorityClassName: ""
# Node selection constraints.
nodeSelector: {}
# Pod tolerations.
tolerations: []
# Pod affinity.
affinity: {}
# Pod topology spread constraints.
topologySpreadConstraints: []
# Pod termination grace period (overrides default 10s).
terminationGracePeriodSeconds: null
# Extra volumes appended to the default ones.
extraVolumes: []
# Extra volume mounts appended to the default ones.
extraVolumeMounts: []
# Raw extra podSpec fields to be merged into spec.template.spec.
# Example:
# extraSpec:
# hostAliases:
# - ip: "127.0.0.1"
# hostnames: ["example.local"]
extraSpec: {}
# Metrics configuration. If omitted, metrics are disabled.
# metrics:
# controllerManagerAddr: ":8080"
# listenerAddr: ":8080"
# listenerEndpoint: "/metrics"

View File

@@ -0,0 +1,24 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
tests/

View File

@@ -0,0 +1,33 @@
apiVersion: v2
name: gha-runner-scale-set-experimental
description: A Helm chart for deploying an AutoScalingRunnerSet
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: "0.14.0"
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.14.0"
home: https://github.com/actions/actions-runner-controller
sources:
- "https://github.com/actions/actions-runner-controller"
maintainers:
- name: actions
url: https://github.com/actions

View File

@@ -0,0 +1,61 @@
{{/*
Create the labels for the autoscaling runner set.
*/}}
{{- define "autoscaling-runner-set.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "autoscaling-runner-set" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.autoscalingRunnerSet.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the autoscaling runner set.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.autoscalingRunnerSet.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "autoscaling-runner-set.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.autoscalingRunnerSet.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
Container spec that is expanded for the runner container
*/}}
{{- define "autoscaling-runner-set.template-runner-container" -}}
{{- if not .Values.runner.container }}
{{ fail "You must provide a runner container specification in values.runner.container" }}
{{- end }}
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) -}}
name: runner
image: {{ .Values.runner.container.image | default "ghcr.io/actions/runner:latest" }}
command: {{ toJson (default (list "/home/runner/run.sh") .Values.runner.container.command) }}
{{- $extra := omit .Values.runner.container "name" "image" "command" -}}
{{- if not (empty $extra) -}}
{{toYaml $extra }}
{{- end -}}
{{- end }}
{{- define "autoscaling-runner-set.template-service-account" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $runnerMode := (index $runner "mode" | default "") -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") -}}
{{- $kubeDefaults := (index $kubeMode "default" | default true) -}}
{{- if ne $runnerMode "kubernetes" }}
{{- include "no-permission-serviceaccount.name" . }}
{{- else if not (empty $kubeServiceAccountName) }}
{{- $kubeServiceAccountName }}
{{- else if $kubeDefaults }}
{{- include "kube-mode-serviceaccount.name" . }}
{{- else }}
{{- fail "runner.kubernetesMode.serviceAccountName must be set when runner.mode is 'kubernetes' and runner.kubernetesMode.default is false" -}}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,117 @@
{{- define "autoscaling-runner-set.name" -}}
{{- $name := .Values.runnerScaleSetName | default .Release.Name | replace "_" "-" | trimSuffix "-" }}
{{- if or (empty $name) (gt (len $name) 45) }}
{{ fail "Autoscaling runner set name must have up to 45 characters" }}
{{- end }}
{{- $name }}
{{- end }}
{{- define "autoscaling-runner-set.namespace" -}}
{{- .Values.namespaceOverride | default .Release.Namespace -}}
{{- end }}
{{/*
The name of the manager Role.
*/}}
{{- define "manager-role.name" -}}
{{- printf "%s-manager-role" (include "autoscaling-runner-set.name" .) -}}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "gha-runner-scale-set.chart" -}}
{{- printf "gha-rs-%s" .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
The name of the GitHub secret used for authentication.
*/}}
{{- define "github-secret.name" -}}
{{- if not (empty .Values.auth.secretName) -}}
{{- .Values.auth.secretName -}}
{{- else -}}
{{- include "autoscaling-runner-set.name" . }}-github-secret
{{- end -}}
{{- end }}
{{/*
The name of the no-permission ServiceAccount.
This ServiceAccount is intended for non-kubernetes runner modes when the user
has not specified an explicit ServiceAccount.
*/}}
{{- define "no-permission-serviceaccount.name" -}}
{{- printf "%s-no-permission" (include "autoscaling-runner-set.name" .) -}}
{{- end }}
{{/*
The name of the kubernetes-mode Role.
Kept intentionally aligned with the legacy chart behavior.
*/}}
{{- define "kube-mode-role.name" -}}
{{- printf "%s-kube-mode" (include "autoscaling-runner-set.name" .) -}}
{{- end }}
{{/*
The name of the kubernetes-mode RoleBinding.
Kept intentionally aligned with the kubernetes-mode Role name.
*/}}
{{- define "kube-mode-role-binding.name" -}}
{{- include "kube-mode-role.name" . -}}
{{- end }}
{{/*
The name of the kubernetes-mode ServiceAccount.
Kept intentionally aligned with the legacy chart behavior.
*/}}
{{- define "kube-mode-serviceaccount.name" -}}
{{- include "kube-mode-role.name" . -}}
{{- end }}
{{/*
Create the common labels used across all resources.
*/}}
{{- define "gha-common-labels" -}}
helm.sh/chart: {{ include "gha-runner-scale-set.chart" . }}
app.kubernetes.io/name: {{ include "autoscaling-runner-set.name" . }}
app.kubernetes.io/instance: {{ include "autoscaling-runner-set.name" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: "gha-rs"
actions.github.com/scale-set-name: {{ include "autoscaling-runner-set.name" . }}
actions.github.com/scale-set-namespace: {{ include "autoscaling-runner-set.namespace" . }}
{{- end }}
{{/*
Get the runner container image.
It defaults to ghcr.io/actions/actions-runner:latest if not specified.
*/}}
{{- define "runner.image" -}}
{{- $runner := .Values.runner.container | default dict -}}
{{- if not (kindIs "map" $runner) -}}
{{- fail "runner.container must be a map/object" -}}
{{- end -}}
{{- $image := $runner.image | default "ghcr.io/actions/actions-runner:latest" -}}
{{- if not (kindIs "string" $image) -}}
{{- fail "runner.container.image must be a string" -}}
{{- end -}}
{{- $image }}
{{- end }}
{{- define "runner.command" -}}
{{- $runner := .Values.runner.container | default dict -}}
{{- if not (kindIs "map" $runner) -}}
{{- fail "runner.container must be a map/object" -}}
{{- end -}}
{{- $command := $runner.command | default (list "/home/runner/run.sh") -}}
{{- if not (kindIs "slice" $command) -}}
{{- fail "runner.container.command must be a list/array" -}}
{{- end -}}
{{- toJson $command -}}
{{- end }}

View File

@@ -0,0 +1,71 @@
{{/*
Create the labels for the GitHub auth secret.
*/}}
{{- define "github-secret.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "github-secret" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the GitHub auth secret.
Only global annotations are applied.
Reserved annotations are excluded.
*/}}
{{- define "github-secret.annotations" -}}
{{- $annotations := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
Create the labels for the no-permission ServiceAccount.
*/}}
{{- define "no-permission-serviceaccount.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "no-permission-serviceaccount" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.noPermissionServiceAccount.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the no-permission ServiceAccount.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.noPermissionServiceAccount.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "no-permission-serviceaccount.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.noPermissionServiceAccount.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
Takes a map of user labels and removes the ones with "actions.github.com/" prefix
*/}}
{{- define "apply-non-reserved-gha-labels-and-annotations" -}}
{{- $userLabels := . -}}
{{- $processed := dict -}}
{{- range $key, $value := $userLabels -}}
{{- if not (hasPrefix "actions.github.com/" $key) -}}
{{- $_ := set $processed $key $value -}}
{{- end -}}
{{- end -}}
{{- if not (empty $processed) -}}
{{- $processed | toYaml }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,26 @@
{{- define "listener-template.pod" -}}
{{- $metadata := .Values.listenerPodTemplate.metadata | default dict -}}
{{- $spec := .Values.listenerPodTemplate.spec | default dict -}}
{{- if and (empty $metadata) (empty $spec) -}}
{{- fail "listenerPodTemplate must have at least metadata or spec defined" -}}
{{- end -}}
{{- with $metadata -}}
metadata:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with $spec -}}
spec:
{{- $containers := (index . "containers" | default (list)) -}}
{{- if empty $containers }}
containers:
- name: listener
{{- else }}
containers:
{{- toYaml $containers | nindent 4 }}
{{- end }}
{{- $rest := (omit . "containers") -}}
{{- if gt (len $rest) 0 }}
{{- toYaml $rest | nindent 2 }}
{{- end }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,69 @@
{{/*
Create the labels for the manager Role.
*/}}
{{- define "manager-role.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "manager-role" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.managerRole.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the manager Role.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.managerRole.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "manager-role.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.managerRole.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
The name of the manager RoleBinding.
Kept intentionally aligned with the manager Role name, mirroring the legacy
chart behavior.
*/}}
{{- define "manager-role-binding.name" -}}
{{- include "manager-role.name" . -}}
{{- end }}
{{/*
Create the labels for the manager RoleBinding.
*/}}
{{- define "manager-role-binding.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "manager-role-binding" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.managerRoleBinding.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the manager RoleBinding.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.managerRoleBinding.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "manager-role-binding.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.managerRoleBinding.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,121 @@
{{- define "runner-mode-dind.runner-container" -}}
name: runner
image: {{ include "runner.image" . | quote }}
command: {{ include "runner.command" . }}
env:
- {{ include "runner-mode-dind.env-docker-host" . | nindent 4 }}
- {{ include "runner-mode-dind.env-wait-for-docker-timeout" . | nindent 4 }}
{{/* TODO:: Should we skip DOCKER_HOST and RUNNER_WAIT_FOR_DOCKER_IN_SECONDS? */}}
{{- with .Values.runner.env }}
{{- toYaml . | nindent 2 }}
{{- end }}
volumeMounts:
- name: work
mountPath: /home/runner/_work
- name: dind-sock
mountPath: {{ include "runner-mode-dind.sock-mount-dir" . | quote }}
{{- end }}
{{- define "runner-mode-dind.dind-container" -}}
{{- $dind := .Values.runner.dind | default dict -}}
name: {{ $dind.container.name | default "dind" }}
image: {{ $dind.container.image | default "docker:dind" | quote }}
args:
{{- include "runner-mode-dind.args" . | nindent 2 }}
env:
- name: DOCKER_GROUP_GID
value: {{ ($dind.dockerGroupId | default "123") | quote }}
securityContext:
{{- if $dind.container.securityContext }}
{{- toYaml $dind.container.securityContext | nindent 2 }}
{{ else }}
{{- toYaml (dict "privileged" true) | nindent 2 }}
{{- end }}
restartPolicy: Always
startupProbe:
{{- include "runner-mode-dind.startup-probe" . | nindent 2 }}
volumeMounts:
- name: work
mountPath: /home/runner/_work
- name: dind-sock
mountPath: {{ include "runner-mode-dind.sock-mount-dir" . | quote }}
{{- if $dind.copyExternals }}
- name: dind-externals
mountPath: /home/runner/externals
{{- end }}
{{- end }}
{{- define "runner-mode-dind.pod-volumes" -}}
- name: work
emptyDir: {}
- name: dind-sock
emptyDir: {}
{{- if .Values.runner.dind.copyExternals }}
- name: dind-externals
emptyDir: {}
{{- end }}
{{- end }}
{{- define "runner-mode-dind.copy-externals" -}}
name: init-dind-externals
image: ghcr.io/actions/actions-runner:latest
command: ["cp", "-r", "/home/runner/externals/.", "/home/runner/tmpDir/"]
volumeMounts:
- name: dind-externals
mountPath: /home/runner/tmpDir
{{- end }}
{{- define "runner-mode-dind.startup-probe" -}}
exec:
command:
- docker
- info
initialDelaySeconds: 0
failureThreshold: 24
periodSeconds: 5
{{- end }}
{{- define "runner-mode-dind.args" -}}
{{- $dind := .Values.runner.dind | default dict -}}
{{- $dockerSock := $dind.dockerSock | default "unix:///var/run/docker.sock" -}}
{{- if not (kindIs "string" $dockerSock) -}}
{{- fail "runner.dind.dockerSock must be a string" -}}
{{- end -}}
- dockerd
- {{ printf "--host=%s" $dockerSock }}
- --group=$(DOCKER_GROUP_GID)
{{- end }}
{{- define "runner-mode-dind.env-docker-host" -}}
{{- $dind := .Values.runner.dind | default dict -}}
{{- $dockerSock := $dind.dockerSock | default "unix:///var/run/docker.sock" -}}
{{- if not (kindIs "string" $dockerSock) -}}
{{- fail "runner.dind.dockerSock must be a string" -}}
{{- end -}}
name: DOCKER_HOST
value: {{ $dockerSock | quote }}
{{- end }}
{{- define "runner-mode-dind.env-wait-for-docker-timeout" -}}
{{- $dind := .Values.runner.dind | default dict -}}
{{- $waitForDockerInSeconds := $dind.waitForDockerInSeconds | default 120 -}}
{{- if not (or (kindIs "int" $waitForDockerInSeconds) (kindIs "int64" $waitForDockerInSeconds) (kindIs "float64" $waitForDockerInSeconds)) -}}
{{- fail "runner.dind.waitForDockerInSeconds must be a number" -}}
{{- end -}}
{{- $waitForDockerInSecondsInt := ($waitForDockerInSeconds | int) -}}
{{- if lt $waitForDockerInSecondsInt 0 -}}
{{- fail "runner.dind.waitForDockerInSeconds must be non-negative" -}}
{{- end -}}
name: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
value: {{ $waitForDockerInSecondsInt | toString | quote }}
{{- end }}
{{- define "runner-mode-dind.sock-mount-dir" -}}
{{- $dind := .Values.runner.dind | default dict -}}
{{- $dockerSock := $dind.dockerSock | default "unix:///var/run/docker.sock" -}}
{{- if not (kindIs "string" $dockerSock) -}}
{{- fail "runner.dind.dockerSock must be a string" -}}
{{- end -}}
{{- $dockerSockPath := trimPrefix "unix://" $dockerSock -}}
{{- dir $dockerSockPath -}}
{{- end }}

View File

@@ -0,0 +1,213 @@
{{- define "runner-mode-kubernetes.runner-container" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $hookPath := (index $kubeMode "hookPath" | default "/home/runner/k8s/index.js") -}}
{{- $extensionRef := (index $kubeMode "extensionRef" | default "") -}}
{{- $extension := (index $kubeMode "extension" | default dict) -}}
{{- $extensionYaml := "" -}}
{{- if kindIs "map" $extension -}}
{{- $extensionYaml = (index $extension "yaml" | default "") -}}
{{- end -}}
{{- $hasExtension := or (not (empty $extensionRef)) (not (empty $extensionYaml)) -}}
{{- $hookTemplatePath := printf "%s/hook-template.yaml" (dir $hookPath) -}}
{{- $setHookTemplateEnv := true -}}
{{- $userEnv := (.Values.runner.env | default list) -}}
{{- if kindIs "slice" $userEnv -}}
{{- range $userEnv -}}
{{- if and (kindIs "map" .) (eq ((index . "name") | default "") "ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE") -}}
{{- $setHookTemplateEnv = false -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not (kindIs "string" $hookPath) -}}
{{- fail "runner.kubernetesMode.hookPath must be a string" -}}
{{- end -}}
{{- if not (kindIs "string" $extensionRef) -}}
{{- fail "runner.kubernetesMode.extensionRef must be a string" -}}
{{- end -}}
{{- if and (empty $extensionRef) (hasKey $kubeMode "extension") (not (kindIs "map" $extension)) -}}
{{- fail "runner.kubernetesMode.extension must be an object when runner.kubernetesMode.extensionRef is empty" -}}
{{- end -}}
{{- if and (empty $extensionRef) (not (kindIs "string" $extensionYaml)) -}}
{{- fail "runner.kubernetesMode.extension.yaml must be a string" -}}
{{- end -}}
{{- $requireJobContainer := true -}}
{{- if hasKey $kubeMode "requireJobContainer" -}}
{{- $requireJobContainer = (index $kubeMode "requireJobContainer") -}}
{{- end -}}
{{- if not (kindIs "bool" $requireJobContainer) -}}
{{- fail "runner.kubernetesMode.requireJobContainer must be a bool" -}}
{{- end -}}
name: runner
image: {{ include "runner.image" . | quote }}
command: {{ include "runner.command" . }}
env:
- name: ACTIONS_RUNNER_CONTAINER_HOOKS
value: {{ $hookPath | quote }}
- name: ACTIONS_RUNNER_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
value: {{ ternary "true" "false" $requireJobContainer | quote }}
{{- if and $hasExtension $setHookTemplateEnv }}
- name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE
value: {{ $hookTemplatePath | quote }}
{{- end }}
{{- with .Values.runner.env }}
{{- toYaml . | nindent 2 }}
{{- end }}
volumeMounts:
- name: work
mountPath: /home/runner/_work
{{- if $hasExtension }}
- name: hook-extension
mountPath: {{ $hookTemplatePath | quote }}
subPath: extension
readOnly: true
{{- end }}
{{- end }}
{{- define "runner-mode-kubernetes.pod-volumes" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $extensionRef := (index $kubeMode "extensionRef" | default "") -}}
{{- $extension := (index $kubeMode "extension" | default dict) -}}
{{- $extensionYaml := "" -}}
{{- if kindIs "map" $extension -}}
{{- $extensionYaml = (index $extension "yaml" | default "") -}}
{{- end -}}
{{- $hasExtension := or (not (empty $extensionRef)) (not (empty $extensionYaml)) -}}
{{- $claim := (index $kubeMode "workVolumeClaim" | default dict) -}}
{{- if and (not (empty $claim)) (not (kindIs "map" $claim)) -}}
{{- fail "runner.kubernetesMode.workVolumeClaim must be a map/object" -}}
{{- end -}}
{{- if not (kindIs "string" $extensionRef) -}}
{{- fail "runner.kubernetesMode.extensionRef must be a string" -}}
{{- end -}}
{{- if and (empty $extensionRef) (hasKey $kubeMode "extension") (not (kindIs "map" $extension)) -}}
{{- fail "runner.kubernetesMode.extension must be an object when runner.kubernetesMode.extensionRef is empty" -}}
{{- end -}}
{{- if and (empty $extensionRef) (not (kindIs "string" $extensionYaml)) -}}
{{- fail "runner.kubernetesMode.extension.yaml must be a string" -}}
{{- end -}}
{{- $defaultClaim := dict "accessModes" (list "ReadWriteOnce") "storageClassName" "local-path" "resources" (dict "requests" (dict "storage" "1Gi")) -}}
{{- $claimSpec := mergeOverwrite $defaultClaim $claim -}}
- name: work
ephemeral:
volumeClaimTemplate:
spec:
{{- toYaml $claimSpec | nindent 8 }}
{{- if $hasExtension }}
- name: hook-extension
configMap:
name: {{ if not (empty $extensionRef) }}{{ $extensionRef | quote }}{{ else }}{{ include "kube-mode-extension.name" . | quote }}{{ end }}
{{- end }}
{{- end }}
{{/*
Create the annotations for the kubernetes-mode ServiceAccount.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.kubernetesModeServiceAccount.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "kube-mode-serviceaccount.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeServiceAccount.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
Create the labels for the kubernetes-mode ServiceAccount.
*/}}
{{- define "kube-mode-serviceaccount.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "kube-mode-serviceaccount" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeServiceAccount.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the labels for the kubernetes-mode Role.
*/}}
{{- define "kube-mode-role.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "kube-mode-role" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeRole.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the kubernetes-mode RoleBinding.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.kubernetesModeRoleBinding.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "kube-mode-role-binding.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeRoleBinding.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{/*
Create the labels for the kubernetes-mode RoleBinding.
*/}}
{{- define "kube-mode-role-binding.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "kube-mode-role-binding" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $userLabels := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeRoleBinding.metadata.labels | default (dict)) | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $userLabels $resourceLabels $commonLabels) }}
{{- end }}
{{/*
Create the annotations for the kubernetes-mode Role.
Order of precedence:
1) resource.all.metadata.annotations
2) resource.kubernetesModeRole.metadata.annotations
Reserved annotations are excluded from both levels.
*/}}
{{- define "kube-mode-role.annotations" -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $resource := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.kubernetesModeRole.metadata.annotations | default (dict))) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $resource -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations }}
{{- end }}
{{- end }}
{{- define "kube-mode-extension.name" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $extension := (index $kubeMode "extension" | default dict) -}}
{{- $meta := (index $extension "metadata" | default dict) -}}
{{- $name := (index $meta "name" | default "") -}}
{{- if not (kindIs "string" $name) -}}
{{- fail "runner.kubernetesMode.extension.metadata.name must be a string" -}}
{{- end -}}
{{- default (printf "%s-hook-extension" (include "autoscaling-runner-set.name" .) | trunc 63 | trimSuffix "-") $name -}}
{{- end }}
{{/*
Create the labels for the hook extension ConfigMap.
*/}}
{{- define "kube-mode-extension.labels" -}}
{{- $resourceLabels := dict "app.kubernetes.io/component" "hook-extension" -}}
{{- $commonLabels := include "gha-common-labels" . | fromYaml -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- toYaml (mergeOverwrite $global $resourceLabels $commonLabels) -}}
{{- end }}

View File

@@ -0,0 +1,65 @@
{{/*
Create labels for the runner Pod template (spec.template.metadata.labels).
Order of precedence:
1) resource.all.metadata.labels
2) runner.pod.metadata.labels
3) common labels (cannot be overridden)
Reserved actions.github.com/* labels are excluded from user/global inputs.
*/}}
{{- define "autoscaling-runner-set.runner-pod.labels" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $pod := (index $runner "pod" | default dict) -}}
{{- if not (kindIs "map" $pod) -}}
{{- fail ".Values.runner.pod must be a map/object" -}}
{{- end -}}
{{- $podMetadata := (index $pod "metadata" | default dict) -}}
{{- if not (kindIs "map" $podMetadata) -}}
{{- fail ".Values.runner.pod.metadata must be a map/object" -}}
{{- end -}}
{{- $userRaw := (index $podMetadata "labels" | default (dict)) -}}
{{- if not (kindIs "map" $userRaw) -}}
{{- fail ".Values.runner.pod.metadata.labels must be a map/object" -}}
{{- end -}}
{{- $global := include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.labels | default (dict)) | fromYaml -}}
{{- $user := include "apply-non-reserved-gha-labels-and-annotations" $userRaw | fromYaml -}}
{{- $common := include "gha-common-labels" . | fromYaml -}}
{{- $labels := mergeOverwrite $global $user $common -}}
{{- if not (empty $labels) -}}
{{- toYaml $labels -}}
{{- end -}}
{{- end }}
{{/*
Create annotations for the runner Pod template (spec.template.metadata.annotations).
Order of precedence:
1) resource.all.metadata.annotations
2) runner.pod.metadata.annotations
Reserved actions.github.com/* annotations are excluded from user/global inputs.
*/}}
{{- define "autoscaling-runner-set.runner-pod.annotations" -}}
{{- $runner := (.Values.runner | default dict) -}}
{{- $pod := (index $runner "pod" | default dict) -}}
{{- if not (kindIs "map" $pod) -}}
{{- fail ".Values.runner.pod must be a map/object" -}}
{{- end -}}
{{- $podMetadata := (index $pod "metadata" | default dict) -}}
{{- if not (kindIs "map" $podMetadata) -}}
{{- fail ".Values.runner.pod.metadata must be a map/object" -}}
{{- end -}}
{{- $userRaw := (index $podMetadata "annotations" | default (dict)) -}}
{{- if not (kindIs "map" $userRaw) -}}
{{- fail ".Values.runner.pod.metadata.annotations must be a map/object" -}}
{{- end -}}
{{- $global := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- $user := (include "apply-non-reserved-gha-labels-and-annotations" $userRaw) | fromYaml -}}
{{- $annotations := mergeOverwrite $global $user -}}
{{- if not (empty $annotations) -}}
{{- toYaml $annotations -}}
{{- end -}}
{{- end }}

View File

@@ -0,0 +1,219 @@
{{- $runner := (.Values.runner | default dict) }}
{{- $runnerMode := (index $runner "mode" | default "") }}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) }}
{{- $dind := (index $runner "dind" | default dict) }}
{{- $kubeDefaults := (index $kubeMode "default" | default true) }}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") }}
{{- $usesKubernetesSecrets := or (not .Values.secretResolution) (eq .Values.secretResolution.type "kubernetes") }}
{{- $runnerPod := (index $runner "pod" | default dict) -}}
{{- if not (kindIs "map" $runnerPod) -}}
{{- fail ".Values.runner.pod must be an object" -}}
{{- end }}
{{- $runnerPodSpec := (index $runnerPod "spec" | default dict) -}}
{{- if not (kindIs "map" $runnerPodSpec) -}}
{{- fail ".Values.runner.pod.spec must be an object" -}}
{{- end }}
{{- $extraContainers := (index $runnerPodSpec "containers" | default list) -}}
{{- if not (kindIs "slice" $extraContainers) -}}
{{- fail ".Values.runner.pod.spec.containers must be a list of container specifications" -}}
{{- end }}
{{- range $extraContainers -}}
{{- if not (kindIs "map" .) -}}
{{- fail ".Values.runner.pod.spec.containers must be a list of container specifications" -}}
{{- end }}
{{- $extraContainerName := (index . "name" | default "") -}}
{{- if empty $extraContainerName -}}
{{- fail ".Values.runner.pod.spec.containers[].name is required" -}}
{{- end }}
{{- if eq $extraContainerName "runner" -}}
{{- fail ".Values.runner.pod.spec.containers[].name must not be 'runner' (reserved)" -}}
{{- end }}
{{- end }}
{{- $extraInitContainers := (index $runnerPodSpec "initContainers" | default list) -}}
{{- if not (kindIs "slice" $extraInitContainers) -}}
{{- fail ".Values.runner.pod.spec.initContainers must be a list of container specifications" -}}
{{- end }}
{{- range $extraInitContainers -}}
{{- if not (kindIs "map" .) -}}
{{- fail ".Values.runner.pod.spec.initContainers must be a list of container specifications" -}}
{{- end }}
{{- $extraInitContainerName := (index . "name" | default "") -}}
{{- if empty $extraInitContainerName -}}
{{- fail ".Values.runner.pod.spec.initContainers[].name is required" -}}
{{- end }}
{{- end }}
{{- $runnerPodSpecExtraFields := (omit $runnerPodSpec "containers" "initContainers" "volumes" "serviceAccountName") -}}
{{- $extraVolumes := (index $runnerPodSpec "volumes" | default list) -}}
{{- if not (kindIs "slice" $extraVolumes) -}}
{{- fail ".Values.runner.pod.spec.volumes must be a list of volume specifications" -}}
{{- end }}
{{- $hasInitContainers := or (gt (len $extraInitContainers) 0) (eq $runnerMode "dind") -}}
{{- $hasVolumes := or (gt (len $extraVolumes) 0) (eq $runnerMode "kubernetes") (eq $runnerMode "dind") -}}
apiVersion: actions.github.com/v1alpha1
kind: AutoscalingRunnerSet
metadata:
name: {{ include "autoscaling-runner-set.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "autoscaling-runner-set.labels" . | nindent 4 }}
annotations:
{{- include "autoscaling-runner-set.annotations" . | nindent 4 }}
actions.github.com/values-hash: {{ toJson .Values | sha256sum | trunc 63 }}
{{- if and $usesKubernetesSecrets (empty .Values.auth.secretName) }}
actions.github.com/cleanup-github-secret-name: {{ include "github-secret.name" . | quote }}
{{- end }}
actions.github.com/cleanup-manager-role-binding: {{ include "manager-role-binding.name" . | quote }}
actions.github.com/cleanup-manager-role-name: {{ include "manager-role.name" . | quote }}
{{- if ne $runnerMode "kubernetes" }}
actions.github.com/cleanup-no-permission-service-account-name: {{ include "no-permission-serviceaccount.name" . | quote }}
{{- end }}
{{- if and (eq $runnerMode "kubernetes") $kubeDefaults (empty $kubeServiceAccountName) }}
actions.github.com/cleanup-kubernetes-mode-role-binding-name: {{ include "kube-mode-role-binding.name" . | quote }}
actions.github.com/cleanup-kubernetes-mode-role-name: {{ include "kube-mode-role.name" . | quote }}
actions.github.com/cleanup-kubernetes-mode-service-account-name: {{ include "kube-mode-serviceaccount.name" . | quote }}
{{- end }}
spec:
githubConfigUrl: {{ required ".Values.auth.url is required" (trimSuffix "/" .Values.auth.url) | quote }}
githubConfigSecret: {{ include "github-secret.name" . | quote }}
runnerGroup: {{ .Values.scaleset.runnerGroup | quote }}
runnerScaleSetName: {{ .Values.scaleset.name | quote }}
{{- if .Values.githubServerTLS }}
githubServerTLS:
{{- with .Values.githubServerTLS.certificateFrom }}
certificateFrom:
configMapKeyRef:
name: {{ .configMapKeyRef.name }}
key: {{ .configMapKeyRef.key }}
{{- end }}
{{- end }}
{{- if and .Values.secretResolution (ne .Values.secretResolution.type "kubernetes") }}
vaultConfig:
type: {{ .Values.secretResolution.type }}
{{- if .Values.secretResolution.proxy }}
proxy: {{- toYaml .Values.secretResolution.proxy | nindent 6 }}
{{- end }}
{{- if eq .Values.secretResolution.type "azureKeyVault" }}
azureKeyVault:
url: {{ .Values.secretResolution.azureKeyVault.url }}
tenantId: {{ .Values.secretResolution.azureKeyVault.tenantId }}
clientId: {{ .Values.secretResolution.azureKeyVault.clientId }}
certificatePath: {{ .Values.secretResolution.azureKeyVault.certificatePath }}
secretKey: {{ .Values.secretResolution.azureKeyVault.secretKey }}
{{- else }}
{{- fail (printf "Unsupported keyVault type: %s" .Values.secretResolution.type) }}
{{- end }}
{{- end }}
{{- if .Values.proxy }}
proxy:
{{- if .Values.proxy.http }}
http:
url: {{ .Values.proxy.http.url }}
{{- if .Values.proxy.http.credentialSecretRef }}
credentialSecretRef: {{ .Values.proxy.http.credentialSecretRef }}
{{- end }}
{{- end }}
{{- if .Values.proxy.https }}
https:
url: {{ .Values.proxy.https.url }}
{{- if .Values.proxy.https.credentialSecretRef }}
credentialSecretRef: {{ .Values.proxy.https.credentialSecretRef }}
{{- end }}
{{- end }}
{{- if and .Values.proxy.noProxy (kindIs "slice" .Values.proxy.noProxy) }}
noProxy: {{ .Values.proxy.noProxy | toYaml | nindent 6}}
{{- end }}
{{- end }}
{{- if and (or (kindIs "int64" .Values.scaleset.minRunners) (kindIs "float64" .Values.scaleset.minRunners)) (or (kindIs "int64" .Values.scaleset.maxRunners) (kindIs "float64" .Values.scaleset.maxRunners)) }}
{{- if gt .Values.scaleset.minRunners .Values.scaleset.maxRunners }}
{{- fail "maxRunners has to be greater or equal to minRunners" }}
{{- end }}
{{- end }}
{{- if or (kindIs "int64" .Values.scaleset.maxRunners) (kindIs "float64" .Values.scaleset.maxRunners)}}
{{- if lt (.Values.scaleset.maxRunners | int) 0 }}
{{- fail "maxRunners has to be greater or equal to 0" }}
{{- end }}
maxRunners: {{ .Values.scaleset.maxRunners | int }}
{{- end }}
{{- if or (kindIs "int64" .Values.scaleset.minRunners) (kindIs "float64" .Values.scaleset.minRunners) }}
{{- if lt (.Values.scaleset.minRunners | int) 0 }}
{{- fail "minRunners has to be greater or equal to 0" }}
{{- end }}
minRunners: {{ .Values.scaleset.minRunners | int }}
{{- end }}
{{- if and .Values.listenerPodTemplate (or .Values.listenerPodTemplate.metadata .Values.listenerPodTemplate.spec) }}
listenerTemplate:
{{- include "listener-template.pod" . | nindent 4}}
{{- end }}
{{- with .Values.listenerMetrics }}
listenerMetrics:
{{- toYaml . | nindent 4 }}
{{- end }}
template:
{{- $runnerPodLabels := (include "autoscaling-runner-set.runner-pod.labels" .) -}}
{{- $runnerPodAnnotations := (include "autoscaling-runner-set.runner-pod.annotations" .) -}}
{{- if or $runnerPodLabels $runnerPodAnnotations }}
metadata:
{{- if $runnerPodLabels }}
labels:
{{- $runnerPodLabels | nindent 8 }}
{{- end }}
{{- if $runnerPodAnnotations }}
annotations:
{{- $runnerPodAnnotations | nindent 8 }}
{{- end }}
{{- end }}
spec:
serviceAccountName: {{ include "autoscaling-runner-set.template-service-account" . | quote }}
{{- if $hasInitContainers }}
initContainers:
{{- if and (eq $runnerMode "dind") $dind.copyExternals }}
- {{ include "runner-mode-dind.copy-externals" . | nindent 10 }}
{{- end }}
{{- range $extraInitContainers }}
- {{ toYaml . | nindent 10 }}
{{- end }}
{{- if eq $runnerMode "dind" }}
- {{ include "runner-mode-dind.dind-container" . | nindent 10 }}
{{- end }}
{{- end }}
containers:
-
{{- if eq $runnerMode "kubernetes" }}
{{- include "runner-mode-kubernetes.runner-container" . | nindent 10 }}
{{- else }}
{{- include "runner-mode-dind.runner-container" . | nindent 10 }}
{{- end }}
{{- if $extraContainers }}
{{- range $extraContainers }}
- {{ toYaml . | nindent 10 }}
{{- end }}
{{- end }}
{{- if $hasVolumes }}
volumes:
{{- if eq $runnerMode "kubernetes" }}
{{- include "runner-mode-kubernetes.pod-volumes" . | nindent 8 }}
{{- else }}
{{- include "runner-mode-dind.pod-volumes" . | nindent 8 }}
{{- end }}
{{- if $extraVolumes }}
{{- range $extraVolumes }}
- {{ toYaml . | nindent 10 }}
{{- end }}
{{- end }}
{{- end }}
{{- if gt (len $runnerPodSpecExtraFields) 0 }}
{{- toYaml $runnerPodSpecExtraFields | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,28 @@
{{- $usesKubernetesSecrets := or (not .Values.secretResolution) (eq .Values.secretResolution.type "kubernetes") -}}
{{- if and (not $usesKubernetesSecrets) (empty .Values.auth.secretName) -}}
{{- fail ".Values.auth.secretName is required when .Values.secretResolution.type is not \"kubernetes\"" -}}
{{- end -}}
{{- if and $usesKubernetesSecrets (empty .Values.auth.secretName) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "github-secret.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "github-secret.labels" . | nindent 4 }}
annotations:
{{- include "github-secret.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
type: Opaque
data:
{{- if not (empty .Values.auth.app.clientId) }}
github_app_id: {{ .Values.auth.app.clientId | toString | b64enc }}
github_app_installation_id: {{ required ".Values.auth.app.installationId is required when using GitHub App auth" .Values.auth.app.installationId | toString | b64enc }}
github_app_private_key: {{ required ".Values.auth.app.privateKey is required when using GitHub App auth" .Values.auth.app.privateKey | toString | b64enc }}
{{- else }}
github_token: {{ required ".Values.auth.githubToken is required when auth.secretName and auth.app.clientId are not set" .Values.auth.githubToken | toString | b64enc }}
{{- end }}
{{- end -}}

View File

@@ -0,0 +1,56 @@
{{- $runner := (.Values.runner | default dict) -}}
{{- $runnerMode := (index $runner "mode" | default "") -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $extensionRef := (index $kubeMode "extensionRef" | default "") -}}
{{- if not (kindIs "string" $extensionRef) -}}
{{- fail "runner.kubernetesMode.extensionRef must be a string" -}}
{{- end -}}
{{- if and (eq $runnerMode "kubernetes") (empty $extensionRef) -}}
{{- $extension := (index $kubeMode "extension" | default dict) -}}
{{- if and (hasKey $kubeMode "extension") (not (kindIs "map" $extension)) -}}
{{- fail "runner.kubernetesMode.extension must be an object" -}}
{{- end -}}
{{- $extensionMeta := dict -}}
{{- $extensionName := "" -}}
{{- $extensionNamespace := "" -}}
{{- $extensionYaml := "" -}}
{{- if kindIs "map" $extension -}}
{{- $extensionMeta = (index $extension "metadata" | default dict) -}}
{{- if not (kindIs "map" $extensionMeta) -}}
{{- fail "runner.kubernetesMode.extension.metadata must be an object" -}}
{{- end -}}
{{- $extensionName = (index $extensionMeta "name" | default "") -}}
{{- $extensionNamespace = (index $extensionMeta "namespace" | default "") -}}
{{- $extensionYaml = (index $extension "yaml" | default "") -}}
{{- end -}}
{{- if not (kindIs "string" $extensionName) -}}
{{- fail "runner.kubernetesMode.extension.metadata.name must be a string" -}}
{{- end -}}
{{- if not (kindIs "string" $extensionNamespace) -}}
{{- fail "runner.kubernetesMode.extension.metadata.namespace must be a string" -}}
{{- end -}}
{{- if not (kindIs "string" $extensionYaml) -}}
{{- fail "runner.kubernetesMode.extension.yaml must be a string" -}}
{{- end -}}
{{- if not (empty $extensionYaml) -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ default (printf "%s-hook-extension" (include "autoscaling-runner-set.name" .) | trunc 63 | trimSuffix "-") $extensionName | quote }}
namespace: {{ default (include "autoscaling-runner-set.namespace" .) $extensionNamespace | quote }}
labels:
{{- include "kube-mode-extension.labels" . | nindent 4 }}
{{- $annotations := (include "apply-non-reserved-gha-labels-and-annotations" (.Values.resource.all.metadata.annotations | default (dict))) | fromYaml -}}
{{- if not (empty $annotations) }}
annotations:
{{- toYaml $annotations | nindent 4 }}
{{- end }}
data:
extension: |-
{{ $extensionYaml | indent 4 }}
{{- end -}}
{{- end -}}

View File

@@ -0,0 +1,42 @@
{{- $runner := (.Values.runner | default dict) -}}
{{- $runnerMode := (index $runner "mode" | default "") -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $kubeDefaults := (index $kubeMode "default" | default true) -}}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") -}}
{{- if and (eq $runnerMode "kubernetes") $kubeDefaults (empty $kubeServiceAccountName) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "kube-mode-role.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "kube-mode-role.labels" . | nindent 4 }}
annotations:
{{- include "kube-mode-role.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create", "delete"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["get", "create"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "create", "delete"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "delete"]
{{- with .Values.resource.kubernetesModeRole.extraRules }}
{{- if not (empty .) }}
{{- if not (kindIs "slice" .) -}}
{{- fail ".Values.resource.kubernetesModeRole.extraRules must be a list of RBAC policy rules" -}}
{{- end }}
{{ toYaml . | nindent 2 }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,26 @@
{{- $runner := (.Values.runner | default dict) -}}
{{- $runnerMode := (index $runner "mode" | default "") -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $kubeDefaults := (index $kubeMode "default" | default true) -}}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") -}}
{{- if and (eq $runnerMode "kubernetes") $kubeDefaults (empty $kubeServiceAccountName) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "kube-mode-role-binding.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "kube-mode-role-binding.labels" . | nindent 4 }}
annotations:
{{- include "kube-mode-role-binding.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "kube-mode-role.name" . | quote }}
subjects:
- kind: ServiceAccount
name: {{ include "kube-mode-serviceaccount.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
{{- end }}

View File

@@ -0,0 +1,18 @@
{{- $runner := (.Values.runner | default dict) -}}
{{- $runnerMode := (index $runner "mode" | default "") -}}
{{- $kubeMode := (index $runner "kubernetesMode" | default dict) -}}
{{- $kubeDefaults := (index $kubeMode "default" | default true) -}}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") -}}
{{- if and (eq $runnerMode "kubernetes") $kubeDefaults (empty $kubeServiceAccountName) }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "kube-mode-serviceaccount.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "kube-mode-serviceaccount.labels" . | nindent 4 }}
annotations:
{{- include "kube-mode-serviceaccount.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
{{- end }}

View File

@@ -0,0 +1,85 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "manager-role.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "manager-role.labels" . | nindent 4 }}
annotations:
{{- include "manager-role.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
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:
- ""
resources:
- serviceaccounts
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 }}
{{- with .Values.resource.managerRole.extraRules }}
{{- if not (empty .) }}
{{- if not (kindIs "slice" .) -}}
{{- fail ".Values.resource.managerRole.extraRules must be a list of RBAC policy rules" -}}
{{- end }}
{{ toYaml . }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,22 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "manager-role-binding.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "manager-role-binding.labels" . | nindent 4 }}
annotations:
{{- include "manager-role-binding.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "manager-role.name" . | quote }}
subjects:
{{- if not .Values.controllerServiceAccount }}
{{- fail "controllerServiceAccount must be set" -}}
{{- end }}
- kind: ServiceAccount
name: {{ required "controllerServiceAccount.name must be set" .Values.controllerServiceAccount.name | quote }}
namespace: {{ required "controllerServiceAccount.namespace must be set" .Values.controllerServiceAccount.namespace | quote }}

View File

@@ -0,0 +1,14 @@
{{- $runnerMode := (.Values.runner.mode | default "") -}}
{{- if ne $runnerMode "kubernetes" -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "no-permission-serviceaccount.name" . | quote }}
namespace: {{ include "autoscaling-runner-set.namespace" . | quote }}
labels:
{{- include "no-permission-serviceaccount.labels" . | nindent 4 }}
annotations:
{{- include "no-permission-serviceaccount.annotations" . | nindent 4 }}
finalizers:
- actions.github.com/cleanup-protection
{{- end }}

View File

@@ -0,0 +1,79 @@
suite: "Test AutoscalingRunnerSet Annotations"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render values-hash annotation
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- exists:
path: metadata.annotations["actions.github.com/values-hash"]
- it: should merge global and resource annotations (resource overrides global)
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
a: "global"
shared: "global"
autoscalingRunnerSet:
metadata:
annotations:
b: "resource"
shared: "resource"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations.a
value: "global"
- equal:
path: metadata.annotations.b
value: "resource"
- equal:
path: metadata.annotations.shared
value: "resource"
- it: should not allow overriding reserved values-hash annotation
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
actions.github.com/values-hash: "user-value"
ok: "ok"
autoscalingRunnerSet:
metadata:
annotations:
actions.github.com/cleanup-something: "should-not-render"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations.ok
value: "ok"
- notEqual:
path: metadata.annotations["actions.github.com/values-hash"]
value: "user-value"
- notExists:
path: metadata.annotations["actions.github.com/cleanup-something"]

View File

@@ -0,0 +1,245 @@
suite: "Test AutoscalingRunnerSet Authentication & Configuration"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should require githubConfigUrl
set:
scaleset.name: "test"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.auth.url is required"
- it: should render githubConfigUrl from auth.url
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubConfigUrl
value: "https://github.com/org"
- it: should trim trailing slash from githubConfigUrl
set:
scaleset.name: "test"
auth.url: "https://github.com/org/"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubConfigUrl
value: "https://github.com/org"
- it: should render default githubConfigSecret from release name
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubConfigSecret
value: "test-name-github-secret"
- it: should render custom githubConfigSecret when auth.secretName is provided
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
auth.secretName: "custom-github-secret"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubConfigSecret
value: "custom-github-secret"
- it: should render default runnerGroup when not configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerGroup
value: "default"
- it: should render custom runnerGroup when configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
scaleset.runnerGroup: "custom-group"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerGroup
value: "custom-group"
- it: should render runnerGroup with special characters
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
scaleset.runnerGroup: "my-custom-runner-group-123"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerGroup
value: "my-custom-runner-group-123"
- it: should render runnerScaleSetName from scaleset.name
set:
scaleset.name: "my-runner-set"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerScaleSetName
value: "my-runner-set"
- it: should use release name as metadata name when runnerScaleSetName not provided
set:
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "release-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.name
value: "release-name"
- it: should use scaleset.name for spec.runnerScaleSetName when provided
set:
scaleset.name: "spec-runner-name"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "release-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerScaleSetName
value: "spec-runner-name"
- it: should not normalize underscores in runnerScaleSetName (underscores are preserved)
set:
scaleset.name: "my_runner_set"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.runnerScaleSetName
value: "my_runner_set"
- it: should reject metadata name exceeding 45 characters
set:
runnerScaleSetName: "this-is-a-very-long-name-that-exceeds-forty-five-characters-long"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: "Autoscaling runner set name must have up to 45 characters"
- it: should handle githubConfigUrl with enterprise GitHub instance
set:
scaleset.name: "test"
auth.url: "https://github.enterprise.com/api/v3"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubConfigUrl
value: "https://github.enterprise.com/api/v3"
- it: should render all configuration together
set:
runnerScaleSetName: "prod-runners"
scaleset.name: "prod-spec-name"
auth.url: "https://github.com/myorg"
auth.githubToken: "gh_token12345"
auth.secretName: "gh-token-secret"
scaleset.runnerGroup: "prod-group"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "prod-scale-set"
namespace: "arc"
asserts:
- equal:
path: spec.githubConfigUrl
value: "https://github.com/myorg"
- equal:
path: spec.githubConfigSecret
value: "gh-token-secret"
- equal:
path: spec.runnerGroup
value: "prod-group"
- equal:
path: spec.runnerScaleSetName
value: "prod-spec-name"
- equal:
path: metadata.name
value: "prod-runners"
- equal:
path: metadata.namespace
value: "arc"

View File

@@ -0,0 +1,342 @@
suite: "AutoscalingRunnerSet dind mode podspec"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render the default dind pod spec (initContainers, runner container, volumes)
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.initContainers[0].name
value: init-dind-externals
- equal:
path: spec.template.spec.initContainers[0].image
value: ghcr.io/actions/actions-runner:latest
- equal:
path: spec.template.spec.initContainers[0].command[0]
value: cp
- equal:
path: spec.template.spec.initContainers[1].name
value: dind
- equal:
path: spec.template.spec.initContainers[1].image
value: docker:dind
- equal:
path: spec.template.spec.initContainers[1].args[0]
value: dockerd
- equal:
path: spec.template.spec.initContainers[1].args[1]
value: --host=unix:///var/run/docker.sock
- equal:
path: spec.template.spec.initContainers[1].args[2]
value: --group=$(DOCKER_GROUP_GID)
- equal:
path: spec.template.spec.initContainers[1].env[0].name
value: DOCKER_GROUP_GID
- equal:
path: spec.template.spec.initContainers[1].env[0].value
value: "123"
- equal:
path: spec.template.spec.initContainers[1].securityContext.privileged
value: true
- equal:
path: spec.template.spec.initContainers[1].startupProbe.exec.command[0]
value: docker
- equal:
path: spec.template.spec.initContainers[1].startupProbe.exec.command[1]
value: info
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[0].name
value: work
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[0].mountPath
value: /home/runner/_work
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[1].name
value: dind-sock
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[1].mountPath
value: /var/run
- equal:
path: spec.template.spec.containers[0].name
value: runner
- equal:
path: spec.template.spec.containers[0].image
value: ghcr.io/actions/actions-runner:latest
- equal:
path: spec.template.spec.containers[0].env[0].name
value: DOCKER_HOST
- equal:
path: spec.template.spec.containers[0].env[0].value
value: unix:///var/run/docker.sock
- equal:
path: spec.template.spec.containers[0].env[1].name
value: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
- equal:
path: spec.template.spec.containers[0].env[1].value
value: "120"
- equal:
path: spec.template.spec.containers[0].volumeMounts[0].name
value: work
- equal:
path: spec.template.spec.containers[0].volumeMounts[0].mountPath
value: /home/runner/_work
- equal:
path: spec.template.spec.containers[0].volumeMounts[1].name
value: dind-sock
- equal:
path: spec.template.spec.containers[0].volumeMounts[1].mountPath
value: /var/run
- contains:
path: spec.template.spec.volumes
content:
name: work
emptyDir: {}
- contains:
path: spec.template.spec.volumes
content:
name: dind-sock
emptyDir: {}
- contains:
path: spec.template.spec.volumes
content:
name: dind-externals
emptyDir: {}
- it: should omit init-dind-externals and dind-externals volume when copyExternals is false
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
dind:
copyExternals: false
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.initContainers[0].name
value: dind
- notExists:
path: spec.template.spec.initContainers[1]
- notExists:
path: spec.template.spec.volumes[2]
- notExists:
path: spec.template.spec.initContainers[0].volumeMounts[2]
- it: should allow overriding dind container name, image, and securityContext
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
dind:
container:
name: "dockerd-custom"
image: "docker:27.3-dind"
securityContext:
privileged: false
runAsUser: 1000
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.initContainers[1].name
value: dockerd-custom
- equal:
path: spec.template.spec.initContainers[1].image
value: docker:27.3-dind
- equal:
path: spec.template.spec.initContainers[1].securityContext.privileged
value: false
- equal:
path: spec.template.spec.initContainers[1].securityContext.runAsUser
value: 1000
- it: should respect dockerSock override in DOCKER_HOST, mountPath, and dind args
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
dind:
dockerSock: "unix:///var/run/custom/docker.sock"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].env[0].name
value: DOCKER_HOST
- equal:
path: spec.template.spec.containers[0].env[0].value
value: unix:///var/run/custom/docker.sock
- equal:
path: spec.template.spec.containers[0].volumeMounts[1].mountPath
value: /var/run/custom
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[1].mountPath
value: /var/run/custom
- equal:
path: spec.template.spec.initContainers[1].args[1]
value: --host=unix:///var/run/custom/docker.sock
- it: should respect waitForDockerInSeconds override
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
dind:
waitForDockerInSeconds: 30
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].env[1].name
value: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
- equal:
path: spec.template.spec.containers[0].env[1].value
value: "30"
- it: should include extraInitContainers in dind mode
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
pod:
spec:
initContainers:
- name: "extra-init-1"
image: "busybox:1.36"
imagePullPolicy: IfNotPresent
command:
- "sh"
- "-c"
args:
- "echo extra-init && env | grep FOO"
env:
- name: FOO
value: BAR
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
securityContext:
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- name: work
mountPath: /work
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.initContainers[0].name
value: init-dind-externals
- equal:
path: spec.template.spec.initContainers[1].name
value: extra-init-1
- equal:
path: spec.template.spec.initContainers[1].image
value: busybox:1.36
- equal:
path: spec.template.spec.initContainers[1].imagePullPolicy
value: IfNotPresent
- equal:
path: spec.template.spec.initContainers[1].command[0]
value: sh
- equal:
path: spec.template.spec.initContainers[1].args[0]
value: echo extra-init && env | grep FOO
- equal:
path: spec.template.spec.initContainers[1].env[0].name
value: FOO
- equal:
path: spec.template.spec.initContainers[1].env[0].value
value: BAR
- equal:
path: spec.template.spec.initContainers[1].resources.requests.cpu
value: 10m
- equal:
path: spec.template.spec.initContainers[1].resources.requests.memory
value: 16Mi
- equal:
path: spec.template.spec.initContainers[1].resources.limits.cpu
value: 100m
- equal:
path: spec.template.spec.initContainers[1].resources.limits.memory
value: 64Mi
- equal:
path: spec.template.spec.initContainers[1].securityContext.runAsNonRoot
value: true
- equal:
path: spec.template.spec.initContainers[1].securityContext.runAsUser
value: 1000
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[0].name
value: work
- equal:
path: spec.template.spec.initContainers[1].volumeMounts[0].mountPath
value: /work
- equal:
path: spec.template.spec.initContainers[2].name
value: dind
- it: should include extraVolumes in dind mode
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "dind"
pod:
spec:
volumes:
- name: "cache"
emptyDir: {}
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: cache
emptyDir: {}

View File

@@ -0,0 +1,84 @@
suite: "AutoscalingRunnerSet extraContainers"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should append extraContainers after the runner container
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
containers:
- name: "sidecar"
image: "busybox:1.36"
command: ["sh", "-c", "sleep 3600"]
resources:
limits:
cpu: "250m"
memory: "64Mi"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].name
value: runner
- equal:
path: spec.template.spec.containers[1].name
value: sidecar
- equal:
path: spec.template.spec.containers[1].image
value: busybox:1.36
- equal:
path: spec.template.spec.containers[1].resources.limits.cpu
value: 250m
- equal:
path: spec.template.spec.containers[1].resources.limits.memory
value: 64Mi
- it: should fail when runner.pod.spec.containers is not a list
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
containers:
name: "not-a-list"
image: "busybox:1.36"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.runner.pod.spec.containers must be a list of container specifications"
- it: should fail when a container is named runner
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
containers:
- name: "runner"
image: "busybox:1.36"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.runner.pod.spec.containers[].name must not be 'runner' (reserved)"

View File

@@ -0,0 +1,84 @@
suite: "AutoscalingRunnerSet initContainers"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render initContainers when runner.pod.spec.initContainers configured in non-dind mode
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
pod:
spec:
initContainers:
- name: "extra-init-1"
image: "busybox:1.36"
imagePullPolicy: IfNotPresent
command:
- "sh"
- "-c"
args:
- "echo non-dind extra init"
env:
- name: FOO
value: BAR
securityContext:
runAsNonRoot: true
runAsUser: 1000
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.initContainers[0].name
value: extra-init-1
- equal:
path: spec.template.spec.initContainers[0].image
value: busybox:1.36
- equal:
path: spec.template.spec.initContainers[0].imagePullPolicy
value: IfNotPresent
- equal:
path: spec.template.spec.initContainers[0].command[0]
value: sh
- equal:
path: spec.template.spec.initContainers[0].args[0]
value: echo non-dind extra init
- equal:
path: spec.template.spec.initContainers[0].env[0].name
value: FOO
- equal:
path: spec.template.spec.initContainers[0].env[0].value
value: BAR
- equal:
path: spec.template.spec.initContainers[0].securityContext.runAsNonRoot
value: true
- equal:
path: spec.template.spec.initContainers[0].securityContext.runAsUser
value: 1000
- equal:
path: spec.template.spec.initContainers[0].resources.requests.cpu
value: 10m
- equal:
path: spec.template.spec.initContainers[0].resources.requests.memory
value: 16Mi
- equal:
path: spec.template.spec.initContainers[0].resources.limits.cpu
value: 100m
- equal:
path: spec.template.spec.initContainers[0].resources.limits.memory
value: 64Mi
- notExists:
path: spec.template.spec.initContainers[1]
- equal:
path: spec.template.spec.containers[0].name
value: runner

View File

@@ -0,0 +1,61 @@
suite: "GitHub Server TLS"
templates:
- autoscalingrunnserset.yaml
- manager_role.yaml
tests:
- it: should render spec.githubServerTLS and allow manager role to read the configmap
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
githubServerTLS:
certificateFrom:
configMapKeyRef:
name: "my-ca-config"
key: "ca.crt"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.githubServerTLS.certificateFrom.configMapKeyRef.name
value: my-ca-config
template: autoscalingrunnserset.yaml
- equal:
path: spec.githubServerTLS.certificateFrom.configMapKeyRef.key
value: ca.crt
template: autoscalingrunnserset.yaml
- contains:
path: rules
content:
apiGroups:
- ""
resources:
- configmaps
verbs:
- get
template: manager_role.yaml
- it: should not include the manager role configmap rule when githubServerTLS is not configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notContains:
path: rules
content:
apiGroups:
- ""
resources:
- configmaps
verbs:
- get
template: manager_role.yaml

View File

@@ -0,0 +1,101 @@
suite: "AutoscalingRunnerSet kubernetes mode hook extension wiring"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should mount hook extension ConfigMap when inline extension.yaml is set
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
kubernetesMode:
hookPath: "/home/runner/k8s/index.js"
extension:
metadata:
name: "my-hook-extension"
namespace: "test-namespace"
yaml: "foo: bar"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE
value: /home/runner/k8s/hook-template.yaml
- contains:
path: spec.template.spec.volumes
content:
name: hook-extension
configMap:
name: my-hook-extension
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: hook-extension
mountPath: /home/runner/k8s/hook-template.yaml
subPath: extension
readOnly: true
- it: should mount hook extension ConfigMap when extensionRef is set
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
kubernetesMode:
extensionRef: "existing-hook-extension"
hookPath: "/home/runner/k8s/index.js"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE
value: /home/runner/k8s/hook-template.yaml
- contains:
path: spec.template.spec.volumes
content:
name: hook-extension
configMap:
name: existing-hook-extension
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: hook-extension
mountPath: /home/runner/k8s/hook-template.yaml
subPath: extension
readOnly: true
- it: should allow using extensionRef without defining inline extension
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
kubernetesMode:
extensionRef: "existing-hook-extension"
extension: "not-a-map"
hookPath: "/home/runner/k8s/index.js"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: hook-extension
configMap:
name: existing-hook-extension

View File

@@ -0,0 +1,133 @@
suite: "AutoscalingRunnerSet kubernetes mode podspec"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render the default kubernetes pod spec (runner container, ephemeral work volume)
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.template.spec.initContainers
- equal:
path: spec.template.spec.serviceAccountName
value: test-name-kube-mode
- equal:
path: spec.template.spec.containers[0].name
value: runner
- equal:
path: spec.template.spec.containers[0].image
value: ghcr.io/actions/actions-runner:latest
- equal:
path: spec.template.spec.containers[0].env[0].name
value: ACTIONS_RUNNER_CONTAINER_HOOKS
- equal:
path: spec.template.spec.containers[0].env[0].value
value: /home/runner/k8s/index.js
- equal:
path: spec.template.spec.containers[0].env[1].name
value: ACTIONS_RUNNER_POD_NAME
- equal:
path: spec.template.spec.containers[0].env[1].valueFrom.fieldRef.fieldPath
value: metadata.name
- equal:
path: spec.template.spec.containers[0].env[2].name
value: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
- equal:
path: spec.template.spec.containers[0].env[2].value
value: "true"
- equal:
path: spec.template.spec.containers[0].volumeMounts[0].name
value: work
- equal:
path: spec.template.spec.containers[0].volumeMounts[0].mountPath
value: /home/runner/_work
- notExists:
path: spec.template.spec.containers[0].volumeMounts[1]
- equal:
path: spec.template.spec.volumes[0].name
value: work
- equal:
path: spec.template.spec.volumes[0].ephemeral.volumeClaimTemplate.spec.accessModes[0]
value: ReadWriteOnce
- equal:
path: spec.template.spec.volumes[0].ephemeral.volumeClaimTemplate.spec.storageClassName
value: local-path
- equal:
path: spec.template.spec.volumes[0].ephemeral.volumeClaimTemplate.spec.resources.requests.storage
value: 1Gi
- it: should allow overriding kubernetes mode hookPath, requireJobContainer, and workVolumeClaim
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
kubernetesMode:
hookPath: "/home/runner/custom/k8s/index.js"
requireJobContainer: false
workVolumeClaim:
storageClassName: "fast-ssd"
resources:
requests:
storage: 10Gi
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.containers[0].env[0].value
value: /home/runner/custom/k8s/index.js
- equal:
path: spec.template.spec.containers[0].env[2].value
value: "false"
- equal:
path: spec.template.spec.volumes[0].ephemeral.volumeClaimTemplate.spec.storageClassName
value: fast-ssd
- equal:
path: spec.template.spec.volumes[0].ephemeral.volumeClaimTemplate.spec.resources.requests.storage
value: 10Gi
- it: should include extraVolumes in kubernetes mode
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
volumes:
- name: cache
emptyDir: {}
- name: custom-config
configMap:
name: example-config
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: cache
emptyDir: {}
- contains:
path: spec.template.spec.volumes
content:
name: custom-config
configMap:
name: example-config

View File

@@ -0,0 +1,293 @@
suite: "Test AutoscalingRunnerSet Labels"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render base labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "autoscaling-runner-set"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should include user-defined labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
autoscalingRunnerSet:
metadata:
labels:
team: "backend"
environment: "production"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["team"]
value: "backend"
- equal:
path: metadata.labels["environment"]
value: "production"
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "autoscaling-runner-set"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should include global labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
labels:
global-team: "platform"
owner: "devops"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["global-team"]
value: "platform"
- equal:
path: metadata.labels["owner"]
value: "devops"
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "autoscaling-runner-set"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should merge both user and global labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
autoscalingRunnerSet:
metadata:
labels:
team: "backend"
environment: "staging"
all:
metadata:
labels:
global-team: "platform"
environment: "production"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["team"]
value: "backend"
- equal:
path: metadata.labels["global-team"]
value: "platform"
- equal:
path: metadata.labels["environment"]
value: "staging"
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "autoscaling-runner-set"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should allow user labels to override global labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
autoscalingRunnerSet:
metadata:
labels:
tier: "frontend"
cost-center: "100"
all:
metadata:
labels:
tier: "backend"
environment: "staging"
cost-center: "200"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["tier"]
value: "frontend"
- equal:
path: metadata.labels["cost-center"]
value: "100"
- equal:
path: metadata.labels["environment"]
value: "staging"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- it: should preserve actions.github.com custom labels from user config
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
autoscalingRunnerSet:
metadata:
labels:
team: "backend"
actions.github.com/custom-label: "user-value"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["team"]
value: "backend"
- notExists:
path: metadata.labels["actions.github.com/custom-label"]
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- it: should preserve actions.github.com custom labels from global config
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
labels:
owner: "devops"
actions.github.com/global-custom: "global-value"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["owner"]
value: "devops"
- notExists:
path: metadata.labels["actions.github.com/global-custom"]
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"

View File

@@ -0,0 +1,55 @@
suite: "Test AutoscalingRunnerSet Listener Metrics"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should not render listenerMetrics when not configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.listenerMetrics
- it: should render listenerMetrics when configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerMetrics:
counters:
gha_started_jobs_total:
labels:
- repository
- organization
histograms:
gha_job_startup_duration_seconds:
buckets:
- 0.1
- 1
- 2.5
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- exists:
path: spec.listenerMetrics
- equal:
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[0]
value: repository
- equal:
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[1]
value: organization
- contains:
path: spec.listenerMetrics.histograms.gha_job_startup_duration_seconds.buckets
content: 0.1
- contains:
path: spec.listenerMetrics.histograms.gha_job_startup_duration_seconds.buckets
content: 2.5

View File

@@ -0,0 +1,46 @@
suite: "AutoscalingRunnerSet listenerPodTemplate"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render listenerTemplate from listenerPodTemplate
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerPodTemplate:
spec:
containers:
- name: listener
image: "ghcr.io/actions/actions-runner-controller/actionsmetricsserver:latest"
securityContext:
runAsUser: 1000
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerTemplate.spec.containers[0].name
value: listener
- equal:
path: spec.listenerTemplate.spec.containers[0].securityContext.runAsUser
value: 1000
- it: should default listenerTemplate containers when not specified
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerPodTemplate:
spec:
restartPolicy: Always
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerTemplate.spec.containers[0].name
value: listener

View File

@@ -0,0 +1,71 @@
suite: "Test AutoscalingRunnerSet MinMax Runners"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should set minRunners and maxRunners correctly
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
scaleset.minRunners: 2
scaleset.maxRunners: 5
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.minRunners
value: 2
- equal:
path: spec.maxRunners
value: 5
- it: should fail when minRunners is greater than maxRunners
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
scaleset.minRunners: 6
scaleset.maxRunners: 5
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate: {}
- it: should work when minRunners equals maxRunners
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
scaleset.minRunners: 5
scaleset.maxRunners: 5
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.minRunners
value: 5
- equal:
path: spec.maxRunners
value: 5
- it: should not set minRunners and maxRunners when not provided
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.minRunners
- notExists:
path: spec.maxRunners

View File

@@ -0,0 +1,290 @@
suite: "Test AutoscalingRunnerSet Proxy Configuration"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should not render proxy section when not configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.proxy
- it: should render http proxy configuration
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- notExists:
path: spec.proxy.https
- it: should render https proxy configuration
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
https:
url: "https://secure-proxy.example.com:3128"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.https.url
value: "https://secure-proxy.example.com:3128"
- notExists:
path: spec.proxy.http
- it: should render both http and https proxy configuration
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
https:
url: "https://secure-proxy.example.com:3128"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- equal:
path: spec.proxy.https.url
value: "https://secure-proxy.example.com:3128"
- it: should render http proxy with credential secret reference
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
credentialSecretRef: "proxy-credentials"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- equal:
path: spec.proxy.http.credentialSecretRef
value: "proxy-credentials"
- it: should render https proxy with credential secret reference
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
https:
url: "https://secure-proxy.example.com:3128"
credentialSecretRef: "secure-proxy-creds"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.https.url
value: "https://secure-proxy.example.com:3128"
- equal:
path: spec.proxy.https.credentialSecretRef
value: "secure-proxy-creds"
- it: should render proxy with noProxy list
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
noProxy:
- "localhost"
- "127.0.0.1"
- ".example.local"
- "10.0.0.0/8"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- contains:
path: spec.proxy.noProxy
content: "localhost"
- contains:
path: spec.proxy.noProxy
content: "127.0.0.1"
- contains:
path: spec.proxy.noProxy
content: ".example.local"
- contains:
path: spec.proxy.noProxy
content: "10.0.0.0/8"
- it: should render complete proxy configuration with all options
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
credentialSecretRef: "proxy-credentials"
https:
url: "https://secure-proxy.example.com:3128"
credentialSecretRef: "secure-proxy-creds"
noProxy:
- "localhost"
- "127.0.0.1"
- ".local"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- equal:
path: spec.proxy.http.credentialSecretRef
value: "proxy-credentials"
- equal:
path: spec.proxy.https.url
value: "https://secure-proxy.example.com:3128"
- equal:
path: spec.proxy.https.credentialSecretRef
value: "secure-proxy-creds"
- contains:
path: spec.proxy.noProxy
content: "localhost"
- contains:
path: spec.proxy.noProxy
content: "127.0.0.1"
- contains:
path: spec.proxy.noProxy
content: ".local"
- it: should render proxy configuration with empty noProxy list
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://proxy.example.com:3128"
noProxy: []
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://proxy.example.com:3128"
- notExists:
path: spec.proxy.noProxy
- it: should not render proxy when configured as empty object
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy: {}
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.proxy
- it: should render proxy with only http without credentials
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
http:
url: "http://unauthenticated-proxy.example.com:8080"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.http.url
value: "http://unauthenticated-proxy.example.com:8080"
- notExists:
path: spec.proxy.http.credentialSecretRef
- notExists:
path: spec.proxy.https
- it: should render proxy with https and noProxy without http
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
proxy:
https:
url: "https://secure-proxy.example.com:3128"
noProxy:
- "internal.example.com"
- "*.local"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.proxy.https.url
value: "https://secure-proxy.example.com:3128"
- notExists:
path: spec.proxy.http
- contains:
path: spec.proxy.noProxy
content: "internal.example.com"
- contains:
path: spec.proxy.noProxy
content: "*.local"

View File

@@ -0,0 +1,87 @@
suite: "AutoscalingRunnerSet runner pod metadata"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render runner.pod.metadata labels and annotations merged with common/global metadata
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
labels:
team: "platform"
annotations:
global-annotation: "1"
runner:
pod:
metadata:
labels:
purpose: "ci"
annotations:
pod-annotation: "2"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: spec.template.metadata.labels["purpose"]
value: "ci"
- equal:
path: spec.template.metadata.labels["team"]
value: "platform"
- equal:
path: spec.template.metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: spec.template.metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: spec.template.metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: spec.template.metadata.annotations["global-annotation"]
value: "1"
- equal:
path: spec.template.metadata.annotations["pod-annotation"]
value: "2"
- it: should drop reserved actions.github.com/* keys from runner.pod.metadata
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
pod:
metadata:
labels:
actions.github.com/scale-set-name: "should-not-override"
ok: "yes"
annotations:
actions.github.com/some-annotation: "should-not-appear"
ok-annotation: "yes"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: spec.template.metadata.labels["ok"]
value: "yes"
- notExists:
path: spec.template.metadata.annotations["actions.github.com/some-annotation"]
- equal:
path: spec.template.metadata.annotations["ok-annotation"]
value: "yes"

View File

@@ -0,0 +1,45 @@
suite: autoscaling runner set runner pod spec initContainers validation
templates:
- templates/autoscalingrunnserset.yaml
tests:
- it: should fail when runner.pod.spec.initContainers is not a list
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
initContainers:
name: "not-a-list"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.runner.pod.spec.initContainers must be a list of container specifications"
- it: should fail when initContainers entry has no name
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
initContainers:
- image: "busybox:1.36"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.runner.pod.spec.initContainers[].name is required"

View File

@@ -0,0 +1,45 @@
suite: AutoscalingRunnerSet runner pod spec passthrough fields
templates:
- templates/autoscalingrunnserset.yaml
tests:
- it: should apply additional runner.pod.spec fields to the runner pod spec
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
nodeSelector:
disktype: ssd
tolerations:
- key: "dedicated"
operator: "Equal"
value: "ci"
effect: "NoSchedule"
priorityClassName: "runner-high"
terminationGracePeriodSeconds: 42
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.spec.nodeSelector.disktype
value: ssd
- equal:
path: spec.template.spec.tolerations[0].key
value: dedicated
- equal:
path: spec.template.spec.tolerations[0].value
value: ci
- equal:
path: spec.template.spec.priorityClassName
value: runner-high
- equal:
path: spec.template.spec.terminationGracePeriodSeconds
value: 42

View File

@@ -0,0 +1,25 @@
suite: autoscaling runner set runner pod spec volumes validation
templates:
- templates/autoscalingrunnserset.yaml
tests:
- it: should fail when runner.pod.spec.volumes is not a list
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
mode: "kubernetes"
pod:
spec:
volumes:
name: "not-a-list"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.runner.pod.spec.volumes must be a list of volume specifications"

View File

@@ -0,0 +1,110 @@
suite: "Test AutoscalingRunnerSet Vault Config"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should not render vaultConfig when secretResolution.type is kubernetes
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
secretResolution:
type: kubernetes
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.vaultConfig
- it: should render azureKeyVault vaultConfig when configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
secretResolution:
type: azureKeyVault
azureKeyVault:
url: "https://myvault.vault.azure.net"
tenantId: "tenant-123"
clientId: "client-456"
certificatePath: "/etc/certs/akv.pem"
secretKey: "secret-key-name"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.vaultConfig.type
value: azureKeyVault
- equal:
path: spec.vaultConfig.azureKeyVault.url
value: "https://myvault.vault.azure.net"
- equal:
path: spec.vaultConfig.azureKeyVault.tenantId
value: "tenant-123"
- equal:
path: spec.vaultConfig.azureKeyVault.clientId
value: "client-456"
- equal:
path: spec.vaultConfig.azureKeyVault.certificatePath
value: "/etc/certs/akv.pem"
- equal:
path: spec.vaultConfig.azureKeyVault.secretKey
value: "secret-key-name"
- it: should render vaultConfig proxy when configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
secretResolution:
type: azureKeyVault
proxy:
http:
url: "http://proxy.example.com:3128"
credentialSecretRef: "proxy-credentials"
noProxy:
- "localhost"
azureKeyVault:
url: "https://myvault.vault.azure.net"
tenantId: "tenant-123"
clientId: "client-456"
certificatePath: "/etc/certs/akv.pem"
secretKey: "secret-key-name"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.vaultConfig.proxy.http.url
value: "http://proxy.example.com:3128"
- equal:
path: spec.vaultConfig.proxy.http.credentialSecretRef
value: "proxy-credentials"
- contains:
path: spec.vaultConfig.proxy.noProxy
content: "localhost"
- notExists:
path: spec.proxy
- it: should fail for unsupported secretResolution.type
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
secretResolution:
type: "hashicorpVault"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: "Unsupported keyVault type: hashicorpVault"

View File

@@ -0,0 +1,49 @@
suite: "Test GitHub Secret Annotations"
templates:
- githubsecret.yaml
tests:
- it: should include global annotations
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
resource:
all:
metadata:
annotations:
a: "global"
shared: "global"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations.a
value: "global"
- equal:
path: metadata.annotations.shared
value: "global"
- it: should drop actions.github.com annotations from global config
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
resource:
all:
metadata:
annotations:
ok: "ok"
actions.github.com/values-hash: "user-value"
actions.github.com/cleanup-something: "should-not-render"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations.ok
value: "ok"
- notExists:
path: metadata.annotations["actions.github.com/values-hash"]
- notExists:
path: metadata.annotations["actions.github.com/cleanup-something"]

View File

@@ -0,0 +1,78 @@
suite: "Test GitHub Secret Data"
templates:
- githubsecret.yaml
tests:
- it: should render PAT token when configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- exists:
path: data.github_token
- equal:
path: data.github_token
value: "Z2hfdG9rZW4xMjM0NQ=="
- notExists:
path: data.github_app_id
- notExists:
path: data.github_app_installation_id
- notExists:
path: data.github_app_private_key
- it: should render GitHub App keys when app is configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.app:
clientId: "123"
installationId: "456"
privateKey: "mykey"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: data.github_token
- equal:
path: data.github_app_id
value: "MTIz"
- equal:
path: data.github_app_installation_id
value: "NDU2"
- equal:
path: data.github_app_private_key
value: "bXlrZXk="
- it: should fail if app is configured without installationId
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.app:
clientId: "123"
privateKey: "mykey"
asserts:
- failedTemplate:
errorMessage: ".Values.auth.app.installationId is required when using GitHub App auth"
- it: should fail if app is configured without privateKey
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.app:
clientId: "123"
installationId: "456"
asserts:
- failedTemplate:
errorMessage: ".Values.auth.app.privateKey is required when using GitHub App auth"
- it: should fail if neither PAT token nor GitHub App is configured
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
asserts:
- failedTemplate:
errorMessage: ".Values.auth.githubToken is required when auth.secretName and auth.app.clientId are not set"

View File

@@ -0,0 +1,129 @@
suite: "Test GitHub Secret Labels"
templates:
- githubsecret.yaml
tests:
- it: should render base labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "github-secret"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should include global labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
resource:
all:
metadata:
labels:
global-team: "platform"
owner: "devops"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["global-team"]
value: "platform"
- equal:
path: metadata.labels["owner"]
value: "devops"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "github-secret"
- it: should drop actions.github.com custom labels from global config
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
resource:
all:
metadata:
labels:
owner: "devops"
actions.github.com/global-custom: "global-value"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["owner"]
value: "devops"
- notExists:
path: metadata.labels["actions.github.com/global-custom"]
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- it: should not allow global labels to override reserved labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
resource:
all:
metadata:
labels:
helm.sh/chart: "bad"
app.kubernetes.io/name: "bad"
app.kubernetes.io/instance: "bad"
app.kubernetes.io/component: "bad"
actions.github.com/scale-set-name: "bad"
actions.github.com/scale-set-namespace: "bad"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "github-secret"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"

View File

@@ -0,0 +1,15 @@
suite: "Test GitHub Secret pre-defined secret"
templates:
- githubsecret.yaml
tests:
- it: should not render githubsecret when auth.secretName is provided
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.secretName: "pre-defined-secret"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,89 @@
suite: "Hook extension ConfigMap"
templates:
- hook_extension.yaml
tests:
- it: should render ConfigMap with data.extension from extension.yaml
set:
runner:
mode: "kubernetes"
kubernetesMode:
extension:
metadata:
name: "my-hook-extension"
namespace: "test-namespace"
yaml: |
foo: bar
nested:
a: 1
release:
name: "test-name"
namespace: "ignored-by-test" # overridden by extension.metadata.namespace above
asserts:
- equal:
path: apiVersion
value: v1
- equal:
path: kind
value: ConfigMap
- equal:
path: metadata.name
value: my-hook-extension
- equal:
path: metadata.namespace
value: test-namespace
- equal:
path: data.extension
value: |-
foo: bar
nested:
a: 1
- it: should not render when extension.yaml is empty
set:
runner:
mode: "kubernetes"
kubernetesMode:
extension:
metadata:
name: "my-hook-extension"
namespace: "test-namespace"
yaml: ""
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should not render when extensionRef is set
set:
runner:
mode: "kubernetes"
kubernetesMode:
extensionRef: "existing-hook-extension"
extension:
metadata:
name: "my-hook-extension"
namespace: "test-namespace"
yaml: |
foo: bar
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should not require inline extension when extensionRef is set
set:
runner:
mode: "kubernetes"
kubernetesMode:
extensionRef: "existing-hook-extension"
extension: "not-a-map"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,182 @@
suite: "Test Kubernetes Mode RoleBinding"
templates:
- kube_mode_role_binding.yaml
tests:
- it: should render base rolebinding metadata in kubernetes mode
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: apiVersion
value: "rbac.authorization.k8s.io/v1"
- equal:
path: kind
value: "RoleBinding"
- equal:
path: metadata.name
value: "test-name-kube-mode"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-role-binding"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- equal:
path: metadata.finalizers[0]
value: "actions.github.com/cleanup-protection"
- equal:
path: roleRef.kind
value: "Role"
- equal:
path: roleRef.name
value: "test-name-kube-mode"
- equal:
path: subjects[0].kind
value: "ServiceAccount"
- equal:
path: subjects[0].name
value: "test-name-kube-mode"
- equal:
path: subjects[0].namespace
value: "test-namespace"
- it: should not render when runner mode is not kubernetes
set:
runner:
mode: "dind"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should not render when serviceAccountName is provided
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: "custom-sa"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should include global and resource labels
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
global-team: "platform"
kubernetesModeRoleBinding:
metadata:
labels:
rb-team: "arc"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["global-team"]
value: "platform"
- equal:
path: metadata.labels["rb-team"]
value: "arc"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-role-binding"
- it: should drop actions.github.com custom labels from config
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
owner: "devops"
actions.github.com/global-custom: "global-value"
kubernetesModeRoleBinding:
metadata:
labels:
actions.github.com/rb-custom: "rb-value"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["owner"]
value: "devops"
- notExists:
path: metadata.labels["actions.github.com/global-custom"]
- notExists:
path: metadata.labels["actions.github.com/rb-custom"]
- it: should not allow overriding reserved labels
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
helm.sh/chart: "bad"
app.kubernetes.io/name: "bad"
app.kubernetes.io/instance: "bad"
app.kubernetes.io/component: "bad"
actions.github.com/scale-set-name: "bad"
actions.github.com/scale-set-namespace: "bad"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-role-binding"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"

View File

@@ -0,0 +1,117 @@
suite: "Test Kubernetes Mode Role"
templates:
- kube_mode_role.yaml
tests:
- it: should render base role metadata in kubernetes mode
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: apiVersion
value: "rbac.authorization.k8s.io/v1"
- equal:
path: kind
value: "Role"
- equal:
path: metadata.name
value: "test-name-kube-mode"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-role"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- equal:
path: metadata.finalizers[0]
value: "actions.github.com/cleanup-protection"
- it: should append extra RBAC policy rules
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
kubernetesModeRole:
extraRules:
- apiGroups:
- ""
resources:
- "events"
verbs:
- "create"
- "patch"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: rules[5].apiGroups[0]
value: ""
- equal:
path: rules[5].resources[0]
value: "events"
- equal:
path: rules[5].verbs[0]
value: "create"
- equal:
path: rules[5].verbs[1]
value: "patch"
- it: should fail when extraRules is not a list
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
kubernetesModeRole:
extraRules: "not-a-list"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.resource.kubernetesModeRole.extraRules must be a list of RBAC policy rules"
- it: should not render when runner mode is not kubernetes
set:
runner:
mode: "dind"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should not render when serviceAccountName is provided
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: "custom-sa"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0

View File

@@ -0,0 +1,167 @@
suite: "Test Kubernetes Mode ServiceAccount"
templates:
- kube_mode_serviceaccount.yaml
tests:
- it: should render base serviceaccount metadata in kubernetes mode
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: apiVersion
value: "v1"
- equal:
path: kind
value: "ServiceAccount"
- equal:
path: metadata.name
value: "test-name-kube-mode"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-serviceaccount"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- equal:
path: metadata.finalizers[0]
value: "actions.github.com/cleanup-protection"
- it: should not render when runner mode is not kubernetes
set:
runner:
mode: "dind"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should not render when serviceAccountName is provided
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: "custom-sa"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- hasDocuments:
count: 0
- it: should include global and resource labels
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
global-team: "platform"
kubernetesModeServiceAccount:
metadata:
labels:
sa-team: "arc"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["global-team"]
value: "platform"
- equal:
path: metadata.labels["sa-team"]
value: "arc"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-serviceaccount"
- it: should drop actions.github.com custom labels from config
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
owner: "devops"
actions.github.com/global-custom: "global-value"
kubernetesModeServiceAccount:
metadata:
labels:
actions.github.com/sa-custom: "sa-value"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["owner"]
value: "devops"
- notExists:
path: metadata.labels["actions.github.com/global-custom"]
- notExists:
path: metadata.labels["actions.github.com/sa-custom"]
- it: should not allow overriding reserved labels
set:
runner:
mode: "kubernetes"
kubernetesMode:
default: true
serviceAccountName: ""
resource:
all:
metadata:
labels:
helm.sh/chart: "bad"
app.kubernetes.io/name: "bad"
app.kubernetes.io/instance: "bad"
app.kubernetes.io/component: "bad"
actions.github.com/scale-set-name: "bad"
actions.github.com/scale-set-namespace: "bad"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "kube-mode-serviceaccount"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"

View File

@@ -0,0 +1,48 @@
suite: "Test Manager RoleBinding Annotations"
templates:
- manager_role_binding.yaml
tests:
- it: should merge global and resource annotations (resource wins)
set:
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
owner: "platform"
environment: "production"
managerRoleBinding:
metadata:
annotations:
environment: "staging"
team: "backend"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.annotations["owner"]
value: "platform"
- equal:
path: metadata.annotations["team"]
value: "backend"
- equal:
path: metadata.annotations["environment"]
value: "staging"
- it: should not allow actions.github.com/* annotations from user input
set:
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
managerRoleBinding:
metadata:
annotations:
actions.github.com/something: "overridden"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: metadata.annotations["actions.github.com/something"]

View File

@@ -0,0 +1,91 @@
suite: "Test Manager RoleBinding Labels"
templates:
- manager_role_binding.yaml
tests:
- it: should render base labels
set:
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["helm.sh/chart"]
value: "gha-rs-0.14.0"
- equal:
path: metadata.labels["app.kubernetes.io/name"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/instance"]
value: "test-name"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "manager-role-binding"
- equal:
path: metadata.labels["app.kubernetes.io/managed-by"]
value: "Helm"
- equal:
path: metadata.labels["app.kubernetes.io/part-of"]
value: "gha-rs"
- equal:
path: metadata.labels["app.kubernetes.io/version"]
value: "0.14.0"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- it: should merge global and resource labels (resource wins)
set:
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
labels:
owner: "platform"
environment: "production"
managerRoleBinding:
metadata:
labels:
environment: "staging"
team: "backend"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["owner"]
value: "platform"
- equal:
path: metadata.labels["team"]
value: "backend"
- equal:
path: metadata.labels["environment"]
value: "staging"
- it: should not allow actions.github.com/* labels from user input
set:
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
managerRoleBinding:
metadata:
labels:
actions.github.com/scale-set-name: "overridden"
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"

View File

@@ -0,0 +1,76 @@
suite: "Test Manager Role Extra Rules"
templates:
- manager_role.yaml
tests:
- it: should render base role metadata
release:
name: "test-name"
namespace: "test-namespace"
chart:
appVersion: "0.14.0"
asserts:
- equal:
path: apiVersion
value: "rbac.authorization.k8s.io/v1"
- equal:
path: kind
value: "Role"
- equal:
path: metadata.name
value: "test-name-manager-role"
- equal:
path: metadata.namespace
value: "test-namespace"
- equal:
path: metadata.labels["app.kubernetes.io/component"]
value: "manager-role"
- equal:
path: metadata.labels["actions.github.com/scale-set-name"]
value: "test-name"
- equal:
path: metadata.labels["actions.github.com/scale-set-namespace"]
value: "test-namespace"
- equal:
path: metadata.finalizers[0]
value: "actions.github.com/cleanup-protection"
- it: should append extra RBAC policy rules
set:
resource:
managerRole:
extraRules:
- apiGroups:
- ""
resources:
- "events"
verbs:
- "create"
- "patch"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: rules[6].apiGroups[0]
value: ""
- equal:
path: rules[6].resources[0]
value: "events"
- equal:
path: rules[6].verbs[0]
value: "create"
- equal:
path: rules[6].verbs[1]
value: "patch"
- it: should fail when extraRules is not a list
set:
resource:
managerRole:
extraRules: "not-a-list"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: ".Values.resource.managerRole.extraRules must be a list of RBAC policy rules"

View File

@@ -0,0 +1,25 @@
suite: "Namespace override"
templates:
- autoscalingrunnserset.yaml
- githubsecret.yaml
tests:
- it: should apply namespaceOverride to AutoscalingRunnerSet and GitHub Secret
set:
namespaceOverride: "override-ns"
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "release-ns"
asserts:
- equal:
path: metadata.namespace
value: "override-ns"
template: autoscalingrunnserset.yaml
- equal:
path: metadata.namespace
value: "override-ns"
template: githubsecret.yaml

View File

@@ -0,0 +1,386 @@
## By default .Release.namespace is used
namespaceOverride: ""
scaleset:
# Name of the scaleset
name: ""
runnerGroup: "default"
## minRunners is the min number of idle runners. The target number of runners created will be
## calculated as a sum of minRunners and the number of jobs assigned to the scale set.
# minRunners: 0
## maxRunners is the max number of runners the autoscaling runner set will scale up to.
# maxRunners: 5
# Auth object provides authorization parameters.
# You should apply either:
# 1) secretName referencing the secret containing authorization parameters in the same namespace where the scale set is being installed in
# 2) app object parameters
# 3) github_tokne
#
# If multiple of them are set, only single one will be applied based on the above mentioned order.
auth:
url: "" # Required
githubToken: ""
secretName: ""
app:
clientId: ""
installationId: ""
privateKey: ""
# secretResolution configures how secrets are resolved for this scale set.
# By default, secrets are resolved using Kubernetes secrets. When Kubernetes
# secrets are used, no proxy config will be applied.
#
# If you decide to use secret integrations with vaults, you can configure
# proxy settings for the vault communication here.
secretResolution:
# Name of the secret resolver to use.
# Available values:
# - "kubernetes" - use Kubernetes secrets
# - "azureKeyVault" - use Azure Key Vault
type: "kubernetes"
## Proxy settings when type is NOT "kubernetes"
# proxy:
# http:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# https:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# noProxy:
# - example.com
# - example.org
## Configuration for Azure Key Vault integration
# azureKeyVault:
# url: ""
# client_id: ""
# tenant_id: ""
# certificate_path: ""
## Proxy can be used to define proxy settings that will be used by the
## controller, the listener and the runner of this scale set.
# proxy:
# http:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# https:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# noProxy:
# - example.com
# - example.org
## listenerTemplate is the PodSpec for each listener Pod
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
# listenerPodTemplate:
# spec:
# containers:
# # Use this section to append additional configuration to the listener container.
# # If you change the name of the container, the configuration will not be applied to the listener,
# # and it will be treated as a side-car container.
# - name: listener
# securityContext:
# runAsUser: 1000
# # Use this section to add the configuration of a side-car container.
# # Comment it out or remove it if you don't need it.
# # Spec for this container will be applied as is without any modifications.
# - name: side-car
# image: example-sidecar
## Resource object allows modifying resources created by the chart itself
resource:
# Specifies metadata that will be applied to all resources managed by ARC
all:
metadata:
labels: {}
annotations: {}
# Specifies metadata that will be applied to the AutoscalingRunnerSet resource
autoscalingRunnerSet:
metadata:
labels: {}
annotations: {}
# Specifies metadata that will be applied to the manager Role resource
managerRole:
metadata:
labels: {}
annotations: {}
extraRules: []
# Specifies metadata that will be applied to the manager RoleBinding resource
managerRoleBinding:
metadata:
labels: {}
annotations: {}
# Specifies metadata that will be applied to the no-permission ServiceAccount
# (created for non-kubernetes runner modes).
noPermissionServiceAccount:
metadata:
labels: {}
annotations: {}
# Specifies metadata that will be applied to the kubernetes-mode RoleBinding
# (created when runner.mode is "kubernetes" and a ServiceAccountName is not provided).
kubernetesModeRoleBinding:
metadata:
labels: {}
annotations: {}
# Specifies metadata that will be applied to the kubernetes-mode Role.
kubernetesModeRole:
metadata:
labels: {}
annotations: {}
extraRules: []
# Specifies metadata that will be applied to the kubernetes-mode ServiceAccount.
kubernetesModeServiceAccount:
metadata:
labels: {}
annotations: {}
# TODO: Add more resource customizations when needed
# Template applied for the runner container
runner:
# Mode can be used to automatically add configuration for the selected mode
# The available modes are:
# - "" (default) - no additional configuration is applied
# - "kubernetes" - configuration for running jobs in Kubernetes mode is applied
# - "dind" - configuration for running jobs in Docker-in-Docker mode is
#
# For each mode, we provide configuration out of the box that works for most use
# cases. You can customize our configuration by modifying the fields below,
# or you can leave mode empty and provide your own complete configuration.
mode: ""
pod:
metadata:
labels: {}
annotations: {}
# Runner pod template customization.
#
# All fields under runner.pod.spec (except those listed below) are applied directly to
# spec.template.spec of the generated runner pod.
#
# Special handling:
# - spec.containers: appended after the generated "runner" container (name "runner" is reserved)
# - spec.initContainers: appended after any generated initContainers (e.g. dind mode)
# - spec.volumes: appended after generated volumes
#
# Note: serviceAccountName is managed by the chart and cannot be overridden via runner.pod.spec.
spec:
# The name "runner" is reserved and must not be used here.
containers: []
initContainers: []
volumes: []
# container field is applied to the container named "runner". You cannot override the name of the runner container
container:
image: "ghcr.io/actions/actions-runner:latest"
command: ["/home/runner/run.sh"]
dind:
copyExternals: true
dockerGroupId: "123"
dockerSock: "unix:///var/run/docker.sock"
waitForDockerInSeconds: 120
container:
image: "docker:dind"
kubernetesMode:
serviceAccountName: ""
hookPath: "/home/runner/k8s/index.js"
requireJobContainer: true
extensionRef: ""
extension:
metadata:
name: ""
namespace: ""
yaml: ""
## A self-signed CA certificate for communication with the GitHub server can be
## provided using a config map key selector. If `runnerMountPath` is set, for
## each runner pod ARC will:
## - create a `github-server-tls-cert` volume containing the certificate
## specified in `certificateFrom`
## - mount that volume on path `runnerMountPath`/{certificate name}
## - set NODE_EXTRA_CA_CERTS environment variable to that same path
## - set RUNNER_UPDATE_CA_CERTS environment variable to "1" (as of version
## 2.303.0 this will instruct the runner to reload certificates on the host)
##
## If any of the above had already been set by the user in the runner pod
## template, ARC will observe those and not overwrite them.
## Example configuration:
#
# githubServerTLS:
# certificateFrom:
# configMapKeyRef:
# name: config-map-name
# key: ca.crt
# runnerMountPath: /usr/local/share/ca-certificates/
## controllerServiceAccount is the service account of the controller
controllerServiceAccount:
namespace: ""
name: ""
## listenerMetrics are configurable metrics applied to the listener.
## In order to avoid helm merging these fields, we left the metrics commented out.
## When configuring metrics, please uncomment the listenerMetrics object below.
## You can modify the configuration to remove the label or specify custom buckets for histogram.
##
## If the buckets field is not specified, the default buckets will be applied. Default buckets are
## provided here for documentation purposes
# listenerMetrics:
# counters:
# gha_started_jobs_total:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# gha_completed_jobs_total:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target",
# ]
# gauges:
# gha_assigned_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_running_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_registered_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_busy_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_min_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_max_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_desired_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_idle_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# histograms:
# gha_job_startup_duration_seconds:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]
# gha_job_execution_duration_seconds:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target"
# ]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]

View File

@@ -21,3 +21,4 @@
.idea/
*.tmproj
.vscode/
tests/

View File

@@ -0,0 +1,9 @@
suite: "AutoscalingRunnerSet"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render base labels
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_"

View File

@@ -274,10 +274,6 @@ func (c *Client) Identifier() string {
func (c *Client) Do(req *http.Request) (*http.Response, error) {
resp, err := c.Client.Do(req)
if err != nil {
// If we have a response even with an error, include the status code
if resp != nil {
return nil, fmt.Errorf("client request failed with status code %d: %w", resp.StatusCode, err)
}
return nil, fmt.Errorf("client request failed: %w", err)
}
@@ -860,8 +856,7 @@ func (c *Client) GenerateJitRunnerConfig(ctx context.Context, jitRunnerSetting *
resp, err := c.Do(req)
if err != nil {
// Include the URL and method in the error for better debugging
return nil, fmt.Errorf("failed to issue the request %s %s: %w", req.Method, req.URL.String(), err)
return nil, fmt.Errorf("failed to issue the request: %w", err)
}
if resp.StatusCode != http.StatusOK {

View File

@@ -2,7 +2,6 @@ package actions_test
import (
"context"
"errors"
"net/http"
"testing"
"time"
@@ -59,37 +58,4 @@ func TestGenerateJitRunnerConfig(t *testing.T) {
assert.NotNil(t, err)
assert.Equalf(t, actualRetry, expectedRetry, "A retry was expected after the first request but got: %v", actualRetry)
})
t.Run("Error includes HTTP method and URL when request fails", func(t *testing.T) {
runnerSettings := &actions.RunnerScaleSetJitRunnerSetting{}
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
client, err := actions.NewClient(
server.configURLForOrg("my-org"),
auth,
actions.WithRetryMax(0), // No retries to get immediate error
actions.WithRetryWaitMax(1*time.Millisecond),
)
require.NoError(t, err)
_, err = client.GenerateJitRunnerConfig(ctx, runnerSettings, 1)
require.NotNil(t, err)
// Verify error message includes HTTP method and URL for better debugging
errMsg := err.Error()
assert.Contains(t, errMsg, "POST", "Error message should include HTTP method")
assert.Contains(t, errMsg, "generatejitconfig", "Error message should include URL path")
// The error might be an ActionsError (if response was received) or a wrapped error (if Do() failed)
// In either case, the error message should include request details
var actionsErr *actions.ActionsError
if errors.As(err, &actionsErr) {
// If we got an ActionsError, verify the status code is included
assert.Equal(t, http.StatusInternalServerError, actionsErr.StatusCode)
}
// If it's a wrapped error from Do(), the error message already includes the method and URL
// which is what we're testing for
})
}

View File

@@ -36,7 +36,7 @@ type ActionsError struct {
}
func (e *ActionsError) Error() string {
return fmt.Sprintf("actions error: StatusCode %d, ActivityId %q: %v", e.StatusCode, e.ActivityID, e.Err)
return fmt.Sprintf("actions error: StatusCode %d, AcivityId %q: %v", e.StatusCode, e.ActivityID, e.Err)
}
func (e *ActionsError) Unwrap() error {
@@ -112,7 +112,7 @@ type MessageQueueTokenExpiredError struct {
}
func (e *MessageQueueTokenExpiredError) Error() string {
return fmt.Sprintf("MessageQueueTokenExpiredError: ActivityId %q, StatusCode %d: %s", e.activityID, e.statusCode, e.msg)
return fmt.Sprintf("MessageQueueTokenExpiredError: AcivityId %q, StatusCode %d: %s", e.activityID, e.statusCode, e.msg)
}
type HttpClientSideError struct {

View File

@@ -22,7 +22,7 @@ func TestActionsError(t *testing.T) {
s := err.Error()
assert.Contains(t, s, "StatusCode 404")
assert.Contains(t, s, "ActivityId \"activity-id\"")
assert.Contains(t, s, "AcivityId \"activity-id\"")
assert.Contains(t, s, "example error description")
})

View File

@@ -1,7 +1,6 @@
#!/bin/bash
DIR="$(dirname "${BASH_SOURCE[0]}")"
DIR="$(realpath "${DIR}")"
TEST_DIR="$(realpath "${DIR}/../test/actions.github.com")"

View File

@@ -0,0 +1,89 @@
#!/bin/bash
set -euo pipefail
DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
ROOT_DIR="$(realpath "${DIR}/../..")"
source "${DIR}/helper.sh"
export VERSION="$(chart_version "${ROOT_DIR}/charts/gha-runner-scale-set-controller-experimental/Chart.yaml")"
SCALE_SET_NAME="anonymous-proxy-$(date +'%M%S')$(((RANDOM + 100) % 100 + 1))"
SCALE_SET_NAMESPACE="arc-runners"
WORKFLOW_FILE="arc-test-workflow.yaml"
ARC_NAME="arc"
ARC_NAMESPACE="arc-systems"
function install_arc() {
echo "Creating namespace ${ARC_NAMESPACE}"
kubectl create namespace "${SCALE_SET_NAMESPACE}"
echo "Installing ARC"
helm install "${ARC_NAME}" \
--namespace "${ARC_NAMESPACE}" \
--create-namespace \
--set controller.manager.container.image="${IMAGE_NAME}:${IMAGE_TAG}" \
"${ROOT_DIR}/charts/gha-runner-scale-set-controller-experimental" \
--debug
if ! NAME="${ARC_NAME}" NAMESPACE="${ARC_NAMESPACE}" wait_for_arc; then
NAMESPACE="${ARC_NAMESPACE}" log_arc
return 1
fi
}
function install_squid() {
echo "Starting squid-proxy"
kubectl apply -f "${DIR}/anonymous-proxy-setup.squid.yaml"
}
function install_scale_set() {
echo "Installing scale set ${SCALE_SET_NAMESPACE}/${SCALE_SET_NAME}"
helm install "${SCALE_SET_NAME}" \
--namespace "${SCALE_SET_NAMESPACE}" \
--create-namespace \
--set controllerServiceAccount.name="${ARC_NAME}-gha-rs-controller" \
--set controllerServiceAccount.namespace="${ARC_NAMESPACE}" \
--set auth.url="https://github.com/${TARGET_ORG}/${TARGET_REPO}" \
--set auth.githubToken="${GITHUB_TOKEN}" \
--set proxy.https.url="http://squid.default.svc.cluster.local:3128" \
--set "proxy.noProxy[0]=10.96.0.1:443" \
"${ROOT_DIR}/charts/gha-runner-scale-set-experimental"
if ! NAME="${SCALE_SET_NAME}" NAMESPACE="${ARC_NAMESPACE}" wait_for_scale_set; then
NAMESPACE="${ARC_NAMESPACE}" log_arc
return 1
fi
}
function main() {
local failed=()
build_image
create_cluster
install_arc
install_squid
install_scale_set || {
echo "Scale set installation failed"
NAMESPACE="${ARC_NAMESPACE}" log_arc
delete_cluster
exit 1
}
WORKFLOW_FILE="${WORKFLOW_FILE}" SCALE_SET_NAME="${SCALE_SET_NAME}" run_workflow || failed+=("run_workflow")
INSTALLATION_NAME="${SCALE_SET_NAME}" NAMESPACE="${SCALE_SET_NAMESPACE}" cleanup_scale_set || failed+=("cleanup_scale_set")
NAMESPACE="${ARC_NAMESPACE}" log_arc || failed+=("log_arc")
delete_cluster
print_results "${failed[@]}"
}
main

View File

@@ -8,6 +8,8 @@ ROOT_DIR="$(realpath "${DIR}/../..")"
source "${DIR}/helper.sh"
export VERSION="$(chart_version "${ROOT_DIR}/charts/gha-runner-scale-set-controller/Chart.yaml")"
SCALE_SET_NAME="anonymous-proxy-$(date +'%M%S')$(((RANDOM + 100) % 100 + 1))"
SCALE_SET_NAMESPACE="arc-runners"
WORKFLOW_FILE="arc-test-workflow.yaml"

Some files were not shown because too many files have changed in this diff Show More