Compare commits

..

17 Commits

Author SHA1 Message Date
Jesse Haka
28e80a2d28 Add support for enterprise runners (#290)
* Add support for enterprise runners

* update docs
2021-02-05 09:31:06 +09:00
Tom Bamford
831db9ee2a Added github.sha to DockerHub push (#286)
* Added GITHUB.RUN_NUMBER to DockerHub push

* switch run_number to sha on docker tag

* re-add mutable tags for backwards compatability

* truncate to short SHA (7 chars)

* behaviour workaround

* use ENV to define sha_short

* use ::set-output to define sha_short

* bump action
2021-02-04 09:29:32 +09:00
Donovan Muller
4d69e0806e Update GitHub runner version (#280) 2021-02-02 14:06:08 +09:00
Donovan Muller
d37cd69e9b feat/helm: Bump appVersion to 0.6.1 release (#272)
* feat/helm: Bump appVersion to 0.6.1 release

* Also bump chart version to trigger a new chart release

Co-authored-by: Yusuke Kuoka <c-ykuoka@zlab.co.jp>
2021-01-29 09:29:43 +09:00
Yusuke Kuoka
a2690aa5cb Update README.md
Follow-up for #275
2021-01-29 09:29:26 +09:00
Clément
da020df0fd docs: fix install installation method (#275) 2021-01-29 09:28:34 +09:00
Jonas Lergell
6c64ae6a01 Actually use 'dockerdContainerResources' to set resources on the dind container (#273) 2021-01-29 09:18:28 +09:00
Yusuke Kuoka
42c7d0489d chart: Bump to 0.2.0 2021-01-25 09:14:49 +09:00
Donovan Muller
b3bef6404c Add support for additional environment variables (#271) 2021-01-25 09:00:03 +09:00
David Young
1127c447c4 Add GitHub Actions to publish helm chart (#257)
* Add chart workflows (#1)

* Add chart workflows

* Fix publishing step in CI

Signed-off-by: David Young <davidy@funkypenguin.co.nz>

* Update CI on push-to-master (#3)

* Put helm installation step in the correct CI job

Signed-off-by: David Young <davidy@funkypenguin.co.nz>

* Put helm installation step in the correct CI job (#4)

* Update on-push-master-publish-chart.yml

* Remove references to certmanager dependency

Signed-off-by: David Young <davidy@funkypenguin.co.nz>

* Add ability to customize kube-rbac-proxy image

Signed-off-by: David Young <davidy@funkypenguin.co.nz>

* Only install cert-manager if we're going to spin up KinD

Signed-off-by: David Young <davidy@funkypenguin.co.nz>
2021-01-24 15:37:01 +09:00
Yusuke Kuoka
ace95d72ab Fix self-update failuers due to /runner/externals mount (#253)
* Fix self-update failuers due to /runner/externals mount

Fixes #252

* Tested Self-update Fixes (#269)

Adding fixes to #253 as confirmed and tested in https://github.com/summerwind/actions-runner-controller/issues/264#issuecomment-764549833 by @jolestar, @achedeuzot and @hfuss 🙇 🍻

Co-authored-by: Hayden Fuss <wifu1234@gmail.com>
2021-01-24 10:58:35 +09:00
Johannes Nicolai
42493d5e01 Adding --name-space parameter in example (#259)
* when setting a GitHub Enterprise server URL without a namespace, an error occurs: "error: the server doesn't have a resource type "controller-manager"
* setting default namespace "actions-runner-system" makes the example work out of the box
2021-01-22 10:12:04 +09:00
Johannes Nicolai
94e8c6ffbf minReplicas <= desiredReplicas <= maxReplicas (#267)
* ensure that minReplicas <= desiredReplicas <= maxReplicas no matter what
* before this change, if the number of runners was much larger than the max number, the applied scale down factor might still result in a desired value > maxReplicas
* if for resource constraints in the cluster, runners would be permanently restarted, the number of runners could go up more than the reverse scale down factor until the next reconciliation round, resulting in a situation where the number of runners climbs up even though it should actually go down
* by checking whether the desiredReplicas is always <= maxReplicas, infinite scaling up loops can be prevented
2021-01-22 10:11:21 +09:00
callum-tait-pbx
563c79c1b9 feat/helm: add manager secret to Helm chart (#254)
* feat: adding maanger secret to Helm

* fix: correcting secret data format

* feat: adding in common labels

* fix: updating default values to have config

The auth config needs to be commented out by default as we don't want to deploy both configs empty. This may break stuff, so we want the user to actively uncomment the auth method they want instead

* chore: updating default format of cert

* chore: wording
2021-01-22 10:03:25 +09:00
Johannes Nicolai
cbb41cbd18 Updating custom container example (#260)
* use latest instead of outdated version
* use sudo for package install (required)
* use sudo for package meta data removal (required)
2021-01-22 09:57:42 +09:00
Johannes Nicolai
64a1a58acf GitHub runner groups have to be created first (#261)
* in contrast to runner labels, GitHub runner groups are not automatically created
2021-01-22 09:52:35 +09:00
Reinier Timmer
524cf1b379 Update runner to v2.275.1 (#239) 2020-12-18 08:38:39 +09:00
34 changed files with 594 additions and 148 deletions

View File

@@ -27,10 +27,14 @@ jobs:
- name: actions-runner-dind - name: actions-runner-dind
dockerfile: dindrunner.Dockerfile dockerfile: dindrunner.Dockerfile
env: env:
RUNNER_VERSION: 2.274.2 RUNNER_VERSION: 2.276.1
DOCKER_VERSION: 19.03.12 DOCKER_VERSION: 19.03.12
DOCKERHUB_USERNAME: ${{ github.repository_owner }} DOCKERHUB_USERNAME: ${{ github.repository_owner }}
steps: steps:
- name: Set outputs
id: vars
run: echo ::set-output name=sha_short::${GITHUB_SHA::7}
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -61,4 +65,5 @@ jobs:
DOCKER_VERSION=${{ env.DOCKER_VERSION }} DOCKER_VERSION=${{ env.DOCKER_VERSION }}
tags: | tags: |
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }} ${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:v${{ env.RUNNER_VERSION }}-${{ steps.vars.outputs.sha_short }}
${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest ${{ env.DOCKERHUB_USERNAME }}/${{ matrix.name }}:latest

View File

@@ -0,0 +1,75 @@
name: Lint and Test Charts
on:
push:
paths:
- 'charts/**'
- '.github/**'
workflow_dispatch:
env:
KUBE_SCORE_VERSION: 1.10.0
HELM_VERSION: v3.4.1
jobs:
lint-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: ${{ env.HELM_VERSION }}
- name: Set up kube-score
run: |
wget https://github.com/zegl/kube-score/releases/download/v${{ env.KUBE_SCORE_VERSION }}/kube-score_${{ env.KUBE_SCORE_VERSION }}_linux_amd64 -O kube-score
chmod 755 kube-score
- name: Kube-score generated manifests
run: helm template --values charts/.ci/values-kube-score.yaml charts/* | ./kube-score score -
--ignore-test pod-networkpolicy
--ignore-test deployment-has-poddisruptionbudget
--ignore-test deployment-has-host-podantiaffinity
--ignore-test container-security-context
--ignore-test pod-probes
--ignore-test container-image-tag
--enable-optional-test container-security-context-privileged
--enable-optional-test container-security-context-readonlyrootfilesystem
# python is a requirement for the chart-testing action below (supports yamllint among other tests)
- uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.0.1
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --config charts/.ci/ct-config.yaml)
if [[ -n "$changed" ]]; then
echo "::set-output name=changed::true"
fi
- name: Run chart-testing (lint)
run: ct lint --config charts/.ci/ct-config.yaml
- name: Create kind cluster
uses: helm/kind-action@v1.0.0
if: steps.list-changed.outputs.changed == 'true'
# We need cert-manager already installed in the cluster because we assume the CRDs exist
- name: Install cert-manager
run: |
helm repo add jetstack https://charts.jetstack.io --force-update
helm install cert-manager jetstack/cert-manager --set installCRDs=true --wait
if: steps.list-changed.outputs.changed == 'true'
- name: Run chart-testing (install)
run: ct install --config charts/.ci/ct-config.yaml

View File

@@ -0,0 +1,101 @@
name: Publish helm chart
on:
push:
branches:
- master
- main # assume that the branch name may change in future
paths:
- 'charts/**'
- '.github/**'
workflow_dispatch:
env:
KUBE_SCORE_VERSION: 1.10.0
HELM_VERSION: v3.4.1
jobs:
lint-chart:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: ${{ env.HELM_VERSION }}
- name: Set up kube-score
run: |
wget https://github.com/zegl/kube-score/releases/download/v${{ env.KUBE_SCORE_VERSION }}/kube-score_${{ env.KUBE_SCORE_VERSION }}_linux_amd64 -O kube-score
chmod 755 kube-score
- name: Kube-score generated manifests
run: helm template --values charts/.ci/values-kube-score.yaml charts/* | ./kube-score score -
--ignore-test pod-networkpolicy
--ignore-test deployment-has-poddisruptionbudget
--ignore-test deployment-has-host-podantiaffinity
--ignore-test container-security-context
--ignore-test pod-probes
--ignore-test container-image-tag
--enable-optional-test container-security-context-privileged
--enable-optional-test container-security-context-readonlyrootfilesystem
# python is a requirement for the chart-testing action below (supports yamllint among other tests)
- uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.0.1
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --config charts/.ci/ct-config.yaml)
if [[ -n "$changed" ]]; then
echo "::set-output name=changed::true"
fi
- name: Run chart-testing (lint)
run: ct lint --config charts/.ci/ct-config.yaml
- name: Create kind cluster
uses: helm/kind-action@v1.0.0
if: steps.list-changed.outputs.changed == 'true'
# We need cert-manager already installed in the cluster because we assume the CRDs exist
- name: Install cert-manager
run: |
helm repo add jetstack https://charts.jetstack.io --force-update
helm install cert-manager jetstack/cert-manager --set installCRDs=true --wait
if: steps.list-changed.outputs.changed == 'true'
- name: Run chart-testing (install)
run: ct install --config charts/.ci/ct-config.yaml
if: steps.list-changed.outputs.changed == 'true'
publish-chart:
runs-on: ubuntu-latest
needs: lint-chart
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.1.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -9,6 +9,10 @@ jobs:
env: env:
DOCKERHUB_USERNAME: ${{ github.repository_owner }} DOCKERHUB_USERNAME: ${{ github.repository_owner }}
steps: steps:
- name: Set outputs
id: vars
run: echo ::set-output name=sha_short::${GITHUB_SHA::7}
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -52,5 +56,7 @@ jobs:
file: Dockerfile file: Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }} tags: |
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}
${{ env.DOCKERHUB_USERNAME }}/actions-runner-controller:${{ env.VERSION }}-${{ steps.vars.outputs.sha_short }}

View File

@@ -14,21 +14,71 @@ actions-runner-controller uses [cert-manager](https://cert-manager.io/docs/insta
- [Installing cert-manager on Kubernetes](https://cert-manager.io/docs/installation/kubernetes/) - [Installing cert-manager on Kubernetes](https://cert-manager.io/docs/installation/kubernetes/)
Install the custom resource and actions-runner-controller itself. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources. Install the custom resource and actions-runner-controller with `kubectl` or `helm`. This will create actions-runner-system namespace in your Kubernetes and deploy the required resources.
`kubectl`:
``` ```
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/latest/download/actions-runner-controller.yaml # REPLACE "v0.16.1" with the latest release
kubectl apply -f https://github.com/summerwind/actions-runner-controller/releases/download/v0.16.1/actions-runner-controller.yaml
```
`helm`:
```
helm repo add actions-runner-controller https://summerwind.github.io/actions-runner-controller
helm upgrade --install -n actions-runner-system actions-runner-controller/actions-runner-controller
``` ```
### Github Enterprise support ### Github Enterprise support
If you use either Github Enterprise Cloud or Server (and have recent enought version supporting Actions), you can use **actions-runner-controller** with those, too. Authentication works same way as with public Github (repo and organization level). If you use either Github Enterprise Cloud or Server, you can use **actions-runner-controller** with those, too.
Authentication works same way as with public Github (repo and organization level).
The minimum version of Github Enterprise Server is 3.0.0 (or rc1/rc2).
In most cases maintainers do not have environment where to test changes and are reliant on the community for testing.
```shell ```shell
kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> kubectl set env deploy controller-manager -c manager GITHUB_ENTERPRISE_URL=<GHEC/S URL> --namespace actions-runner-system
``` ```
[Enterprise level](https://docs.github.com/en/enterprise-server@2.22/actions/hosting-your-own-runners/adding-self-hosted-runners#adding-a-self-hosted-runner-to-an-enterprise) runners are not working yet as there's no API definition for those. #### Enterprise runners usage
In order to use enterprise runners you must have Admin access to Github Enterprise and you should do Personal Access Token (PAT)
with `enterprise:admin` access. Enterprise runners are not possible to run with Github APP or any other permission.
When you use enterprise runners those will get access to Github Organisations. However, access to the repositories is **NOT**
allowed by default. Each Github Organisation must allow Enterprise runner groups to be used in repositories.
This is needed only one time and is permanent after that.
Example:
```yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: ghe-runner-deployment
spec:
replicas: 2
template:
spec:
enterprise: your-enterprise-name
dockerdWithinRunnerContainer: true
resources:
limits:
cpu: "4000m"
memory: "2Gi"
requests:
cpu: "200m"
memory: "200Mi"
volumeMounts:
- mountPath: /runner
name: runner
volumes:
- name: runner
emptyDir: {}
```
## Setting up authentication with GitHub API ## Setting up authentication with GitHub API
@@ -409,7 +459,7 @@ Note that if you specify `self-hosted` in your workflow, then this will run your
## Runner Groups ## Runner Groups
Runner groups can be used to limit which repositories are able to use the GitHub Runner at an Organisation level. Runner groups can be used to limit which repositories are able to use the GitHub Runner at an Organisation level. Runner groups have to be [created in GitHub first](https://docs.github.com/en/actions/hosting-your-own-runners/managing-access-to-self-hosted-runners-using-groups) before they can be referenced.
To add the runner to the group `NewGroup`, specify the group in your `Runner` or `RunnerDeployment` spec. To add the runner to the group `NewGroup`, specify the group in your `Runner` or `RunnerDeployment` spec.
@@ -468,11 +518,11 @@ The virtual environments from GitHub contain a lot more software packages (diffe
If there is a need to include packages in the runner image for which there is no setup action, then this can be achieved by building a custom container image for the runner. The easiest way is to start with the `summerwind/actions-runner` image and installing the extra dependencies directly in the docker image: If there is a need to include packages in the runner image for which there is no setup action, then this can be achieved by building a custom container image for the runner. The easiest way is to start with the `summerwind/actions-runner` image and installing the extra dependencies directly in the docker image:
```shell ```shell
FROM summerwind/actions-runner:v2.169.1 FROM summerwind/actions-runner:latest
RUN sudo apt update -y \ RUN sudo apt update -y \
&& apt install YOUR_PACKAGE && sudo apt install YOUR_PACKAGE
&& rm -rf /var/lib/apt/lists/* && sudo rm -rf /var/lib/apt/lists/*
``` ```
You can then configure the runner to use a custom docker image by configuring the `image` field of a `Runner` or `RunnerDeployment`: You can then configure the runner to use a custom docker image by configuring the `image` field of a `Runner` or `RunnerDeployment`:

View File

@@ -25,6 +25,10 @@ import (
// RunnerSpec defines the desired state of Runner // RunnerSpec defines the desired state of Runner
type RunnerSpec struct { type RunnerSpec struct {
// +optional
// +kubebuilder:validation:Pattern=`^[^/]+$`
Enterprise string `json:"enterprise,omitempty"`
// +optional // +optional
// +kubebuilder:validation:Pattern=`^[^/]+$` // +kubebuilder:validation:Pattern=`^[^/]+$`
Organization string `json:"organization,omitempty"` Organization string `json:"organization,omitempty"`
@@ -92,12 +96,22 @@ type RunnerSpec struct {
// ValidateRepository validates repository field. // ValidateRepository validates repository field.
func (rs *RunnerSpec) ValidateRepository() error { func (rs *RunnerSpec) ValidateRepository() error {
// Organization and repository are both exclusive. // Enterprise, Organization and repository are both exclusive.
if len(rs.Organization) == 0 && len(rs.Repository) == 0 { foundCount := 0
return errors.New("Spec needs organization or repository") if len(rs.Organization) > 0 {
foundCount += 1
} }
if len(rs.Organization) > 0 && len(rs.Repository) > 0 { if len(rs.Repository) > 0 {
return errors.New("Spec cannot have both organization and repository") foundCount += 1
}
if len(rs.Enterprise) > 0 {
foundCount += 1
}
if foundCount == 0 {
return errors.New("Spec needs enterprise, organization or repository")
}
if foundCount > 1 {
return errors.New("Spec cannot have many fields defined enterprise, organization and repository")
} }
return nil return nil
@@ -113,6 +127,7 @@ type RunnerStatus struct {
// RunnerStatusRegistration contains runner registration status // RunnerStatusRegistration contains runner registration status
type RunnerStatusRegistration struct { type RunnerStatusRegistration struct {
Enterprise string `json:"enterprise,omitempty"`
Organization string `json:"organization,omitempty"` Organization string `json:"organization,omitempty"`
Repository string `json:"repository,omitempty"` Repository string `json:"repository,omitempty"`
Labels []string `json:"labels,omitempty"` Labels []string `json:"labels,omitempty"`
@@ -122,6 +137,7 @@ type RunnerStatusRegistration struct {
// +kubebuilder:object:root=true // +kubebuilder:object:root=true
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
// +kubebuilder:printcolumn:JSONPath=".spec.enterprise",name=Enterprise,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string // +kubebuilder:printcolumn:JSONPath=".spec.organization",name=Organization,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string // +kubebuilder:printcolumn:JSONPath=".spec.repository",name=Repository,type=string
// +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string // +kubebuilder:printcolumn:JSONPath=".spec.labels",name=Labels,type=string

View File

@@ -0,0 +1,4 @@
# This file defines the config for "ct" (chart tester) used by the helm linting GitHub workflow
lint-conf: charts/.ci/lint-config.yaml
chart-repos:
- jetstack=https://charts.jetstack.io

View File

@@ -0,0 +1,6 @@
rules:
# One blank line is OK
empty-lines:
max-start: 1
max-end: 1
max: 1

View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker run --rm -it -w /repo -v $(pwd):/repo quay.io/helmpack/chart-testing ct lint --all --config charts/.ci/ct-config.yaml

View File

@@ -0,0 +1,15 @@
#!/bin/bash
for chart in `ls charts`;
do
helm template --values charts/$chart/ci/ci-values.yaml charts/$chart | kube-score score - \
--ignore-test pod-networkpolicy \
--ignore-test deployment-has-poddisruptionbudget \
--ignore-test deployment-has-host-podantiaffinity \
--ignore-test pod-probes \
--ignore-test container-image-tag \
--enable-optional-test container-security-context-privileged \
--enable-optional-test container-security-context-readonlyrootfilesystem \
--ignore-test container-security-context
done

View File

@@ -15,9 +15,22 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0 version: 0.2.1
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 0.11.2 appVersion: 0.16.1
home: https://github.com/summerwind/actions-runner-controller
sources:
- https://github.com/summerwind/actions-runner-controller
maintainers:
- name: summerwind
email: contact@summerwind.jp
url: https://github.com/summerwind
- name: funkypenguin
email: davidy@funkypenguin.co.nz
url: https://www.funkypenguin.co.nz

View File

@@ -0,0 +1,27 @@
# This file sets some opinionated values for kube-score to use
# when parsing the chart
image:
pullPolicy: Always
podSecurityContext:
fsGroup: 2000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 2000
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128Mi
# Set the following to true to create a dummy secret, allowing the manager pod to start
# This is only useful in CI
createDummySecret: true

View File

@@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@@ -7,6 +7,9 @@ metadata:
name: runners.actions.summerwind.dev name: runners.actions.summerwind.dev
spec: spec:
additionalPrinterColumns: additionalPrinterColumns:
- JSONPath: .spec.enterprise
name: Enterprise
type: string
- JSONPath: .spec.organization - JSONPath: .spec.organization
name: Organization name: Organization
type: string type: string
@@ -419,6 +422,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.
@@ -1541,6 +1547,8 @@ spec:
registration: registration:
description: RunnerStatusRegistration contains runner registration status description: RunnerStatusRegistration contains runner registration status
properties: properties:
enterprise:
type: string
expiresAt: expiresAt:
format: date-time format: date-time
type: string type: string

View File

@@ -89,7 +89,7 @@ Create the name of the service account to use
{{- end }} {{- end }}
{{- define "actions-runner-controller.authProxyServiceName" -}} {{- define "actions-runner-controller.authProxyServiceName" -}}
{{- include "actions-runner-controller.fullname" . }}-controller-manager-metrics-service {{- include "actions-runner-controller.fullname" . }}-metrics-service
{{- end }} {{- end }}
{{- define "actions-runner-controller.selfsignedIssuerName" -}} {{- define "actions-runner-controller.selfsignedIssuerName" -}}

View File

@@ -0,0 +1,10 @@
# This template only exists to facilitate CI testing of the chart, since
# a secret is expected to be found in the namespace by the controller manager
{{ if .Values.createDummySecret -}}
apiVersion: v1
data:
github_token: dGVzdA==
kind: Secret
metadata:
name: controller-manager
{{- end }}

View File

@@ -57,6 +57,10 @@ spec:
optional: true optional: true
- name: GITHUB_APP_PRIVATE_KEY - name: GITHUB_APP_PRIVATE_KEY
value: /etc/actions-runner-controller/github_app_private_key value: /etc/actions-runner-controller/github_app_private_key
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}" image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (cat "v" .Chart.AppVersion | replace " " "") }}"
name: manager name: manager
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
@@ -66,10 +70,14 @@ spec:
protocol: TCP protocol: TCP
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
volumeMounts: volumeMounts:
- mountPath: "/etc/actions-runner-controller" - mountPath: "/etc/actions-runner-controller"
name: controller-manager name: controller-manager
readOnly: true readOnly: true
- mountPath: /tmp
name: tmp
- mountPath: /tmp/k8s-webhook-server/serving-certs - mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert name: cert
readOnly: true readOnly: true
@@ -78,11 +86,16 @@ spec:
- "--upstream=http://127.0.0.1:8080/" - "--upstream=http://127.0.0.1:8080/"
- "--logtostderr=true" - "--logtostderr=true"
- "--v=10" - "--v=10"
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1 image: "{{ .Values.kube_rbac_proxy.image.repository }}:{{ .Values.kube_rbac_proxy.image.tag }}"
name: kube-rbac-proxy name: kube-rbac-proxy
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: ports:
- containerPort: 8443 - containerPort: 8443
name: https name: https
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
terminationGracePeriodSeconds: 10 terminationGracePeriodSeconds: 10
volumes: volumes:
- name: controller-manager - name: controller-manager
@@ -92,6 +105,8 @@ spec:
secret: secret:
defaultMode: 420 defaultMode: 420
secretName: webhook-server-cert secretName: webhook-server-cert
- name: tmp
emptyDir: {}
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}

View File

@@ -0,0 +1,14 @@
{{- if or .Values.authSecret.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: controller-manager
namespace: {{ .Release.Namespace }}
labels:
{{- include "actions-runner-controller.labels" . | nindent 4 }}
type: Opaque
data:
{{- range $k, $v := .Values.authSecret }}
{{ $k }}: {{ $v | toString | b64enc }}
{{- end }}
{{- end }}

View File

@@ -8,6 +8,17 @@ replicaCount: 1
syncPeriod: 10m syncPeriod: 10m
# Only 1 authentication method can be deployed at a time
# Uncomment the configuration you are applying and fill in the details
authSecret:
enabled: false
### GitHub Apps Configuration
#github_app_id: ""
#github_app_installation_id: ""
#github_app_private_key: |
### GitHub PAT Configuration
#github_token: ""
image: image:
repository: summerwind/actions-runner-controller repository: summerwind/actions-runner-controller
# Overrides the manager image tag whose default is the chart appVersion if the tag key is commented out # Overrides the manager image tag whose default is the chart appVersion if the tag key is commented out
@@ -15,6 +26,11 @@ image:
dindSidecarRepositoryAndTag: "docker:dind" dindSidecarRepositoryAndTag: "docker:dind"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
kube_rbac_proxy:
image:
repository: gcr.io/kubebuilder/kube-rbac-proxy
tag: v0.4.1
imagePullSecrets: [] imagePullSecrets: []
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""
@@ -87,3 +103,8 @@ affinity: {}
# ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ # ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
# PriorityClass: system-cluster-critical # PriorityClass: system-cluster-critical
priorityClassName: "" priorityClassName: ""
env: {}
# http_proxy: "proxy.com:8080"
# https_proxy: "proxy.com:8080"
# no_proxy: ""

View File

@@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@@ -426,6 +426,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.

View File

@@ -7,6 +7,9 @@ metadata:
name: runners.actions.summerwind.dev name: runners.actions.summerwind.dev
spec: spec:
additionalPrinterColumns: additionalPrinterColumns:
- JSONPath: .spec.enterprise
name: Enterprise
type: string
- JSONPath: .spec.organization - JSONPath: .spec.organization
name: Organization name: Organization
type: string type: string
@@ -419,6 +422,9 @@ spec:
type: object type: object
dockerdWithinRunnerContainer: dockerdWithinRunnerContainer:
type: boolean type: boolean
enterprise:
pattern: ^[^/]+$
type: string
env: env:
items: items:
description: EnvVar represents an environment variable present in a Container. description: EnvVar represents an environment variable present in a Container.
@@ -1541,6 +1547,8 @@ spec:
registration: registration:
description: RunnerStatusRegistration contains runner registration status description: RunnerStatusRegistration contains runner registration status
properties: properties:
enterprise:
type: string
expiresAt: expiresAt:
format: date-time format: date-time
type: string type: string

View File

@@ -204,7 +204,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
} }
// ListRunners will return all runners managed by GitHub - not restricted to ns // ListRunners will return all runners managed by GitHub - not restricted to ns
runners, err := r.GitHubClient.ListRunners(ctx, orgName, "") runners, err := r.GitHubClient.ListRunners(ctx, "", orgName, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -219,23 +219,19 @@ func (r *HorizontalRunnerAutoscalerReconciler) calculateReplicasByPercentageRunn
var desiredReplicas int var desiredReplicas int
fractionBusy := float64(numRunnersBusy) / float64(numRunners) fractionBusy := float64(numRunnersBusy) / float64(numRunners)
if fractionBusy >= scaleUpThreshold { if fractionBusy >= scaleUpThreshold {
scaleUpReplicas := int(math.Ceil(float64(numRunners) * scaleUpFactor)) desiredReplicas = int(math.Ceil(float64(numRunners) * scaleUpFactor))
if scaleUpReplicas > maxReplicas {
desiredReplicas = maxReplicas
} else {
desiredReplicas = scaleUpReplicas
}
} else if fractionBusy < scaleDownThreshold { } else if fractionBusy < scaleDownThreshold {
scaleDownReplicas := int(float64(numRunners) * scaleDownFactor) desiredReplicas = int(float64(numRunners) * scaleDownFactor)
if scaleDownReplicas < minReplicas {
desiredReplicas = minReplicas
} else {
desiredReplicas = scaleDownReplicas
}
} else { } else {
desiredReplicas = *rd.Spec.Replicas desiredReplicas = *rd.Spec.Replicas
} }
if desiredReplicas < minReplicas {
desiredReplicas = minReplicas
} else if desiredReplicas > maxReplicas {
desiredReplicas = maxReplicas
}
r.Log.V(1).Info( r.Log.V(1).Info(
"Calculated desired replicas", "Calculated desired replicas",
"computed_replicas_desired", desiredReplicas, "computed_replicas_desired", desiredReplicas,

View File

@@ -95,7 +95,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if removed { if removed {
if len(runner.Status.Registration.Token) > 0 { if len(runner.Status.Registration.Token) > 0 {
ok, err := r.unregisterRunner(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) ok, err := r.unregisterRunner(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to unregister runner") log.Error(err, "Failed to unregister runner")
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -194,7 +194,7 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
runnerBusy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) runnerBusy, err := r.isRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to check if runner is busy") log.Error(err, "Failed to check if runner is busy")
return ctrl.Result{}, nil return ctrl.Result{}, nil
@@ -227,8 +227,8 @@ func (r *RunnerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -242,8 +242,8 @@ func (r *RunnerReconciler) isRunnerBusy(ctx context.Context, org, repo, name str
return false, fmt.Errorf("runner not found") return false, fmt.Errorf("runner not found")
} }
func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReconciler) unregisterRunner(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -263,7 +263,7 @@ func (r *RunnerReconciler) unregisterRunner(ctx context.Context, org, repo, name
return false, nil return false, nil
} }
if err := r.GitHubClient.RemoveRunner(ctx, org, repo, id); err != nil { if err := r.GitHubClient.RemoveRunner(ctx, enterprise, org, repo, id); err != nil {
return false, err return false, err
} }
@@ -277,7 +277,7 @@ func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v
log := r.Log.WithValues("runner", runner.Name) log := r.Log.WithValues("runner", runner.Name)
rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) rt, err := r.GitHubClient.GetRegistrationToken(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed") r.Recorder.Event(&runner, corev1.EventTypeWarning, "FailedUpdateRegistrationToken", "Updating registration token failed")
log.Error(err, "Failed to get new registration token") log.Error(err, "Failed to get new registration token")
@@ -339,6 +339,10 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
Name: "RUNNER_REPO", Name: "RUNNER_REPO",
Value: runner.Spec.Repository, Value: runner.Spec.Repository,
}, },
{
Name: "RUNNER_ENTERPRISE",
Value: runner.Spec.Enterprise,
},
{ {
Name: "RUNNER_LABELS", Name: "RUNNER_LABELS",
Value: strings.Join(runner.Spec.Labels, ","), Value: strings.Join(runner.Spec.Labels, ","),
@@ -426,6 +430,9 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
} }
if !dockerdInRunner && dockerEnabled { if !dockerdInRunner && dockerEnabled {
runnerVolumeName := "runner"
runnerVolumeMountPath := "/runner"
pod.Spec.Volumes = []corev1.Volume{ pod.Spec.Volumes = []corev1.Volume{
{ {
Name: "work", Name: "work",
@@ -434,7 +441,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
}, },
}, },
{ {
Name: "externals", Name: runnerVolumeName,
VolumeSource: corev1.VolumeSource{ VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{}, EmptyDir: &corev1.EmptyDirVolumeSource{},
}, },
@@ -452,8 +459,8 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
MountPath: workDir, MountPath: workDir,
}, },
{ {
Name: "externals", Name: runnerVolumeName,
MountPath: "/runner/externals", MountPath: runnerVolumeMountPath,
}, },
{ {
Name: "certs-client", Name: "certs-client",
@@ -484,8 +491,8 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
MountPath: workDir, MountPath: workDir,
}, },
{ {
Name: "externals", Name: runnerVolumeName,
MountPath: "/runner/externals", MountPath: runnerVolumeMountPath,
}, },
{ {
Name: "certs-client", Name: "certs-client",
@@ -501,6 +508,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
SecurityContext: &corev1.SecurityContext{ SecurityContext: &corev1.SecurityContext{
Privileged: &privileged, Privileged: &privileged,
}, },
Resources: runner.Spec.DockerdContainerResources,
}) })
} }

View File

@@ -52,7 +52,7 @@ type RunnerReplicaSetReconciler struct {
func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background() ctx := context.Background()
log := r.Log.WithValues("runner", req.NamespacedName) log := r.Log.WithValues("runnerreplicaset", req.NamespacedName)
var rs v1alpha1.RunnerReplicaSet var rs v1alpha1.RunnerReplicaSet
if err := r.Get(ctx, req.NamespacedName, &rs); err != nil { if err := r.Get(ctx, req.NamespacedName, &rs); err != nil {
@@ -102,7 +102,7 @@ func (r *RunnerReplicaSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
// get runners that are currently not busy // get runners that are currently not busy
var notBusy []v1alpha1.Runner var notBusy []v1alpha1.Runner
for _, runner := range myRunners { for _, runner := range myRunners {
busy, err := r.isRunnerBusy(ctx, runner.Spec.Organization, runner.Spec.Repository, runner.Name) busy, err := r.isRunnerBusy(ctx, runner.Spec.Enterprise, runner.Spec.Organization, runner.Spec.Repository, runner.Name)
if err != nil { if err != nil {
log.Error(err, "Failed to check if runner is busy") log.Error(err, "Failed to check if runner is busy")
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -187,8 +187,8 @@ func (r *RunnerReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r) Complete(r)
} }
func (r *RunnerReplicaSetReconciler) isRunnerBusy(ctx context.Context, org, repo, name string) (bool, error) { func (r *RunnerReplicaSetReconciler) isRunnerBusy(ctx context.Context, enterprise, org, repo, name string) (bool, error) {
runners, err := r.GitHubClient.ListRunners(ctx, org, repo) runners, err := r.GitHubClient.ListRunners(ctx, enterprise, org, repo)
r.Log.Info("runners", "github", runners) r.Log.Info("runners", "github", runners)
if err != nil { if err != nil {
return false, err return false, err

View File

@@ -78,7 +78,7 @@ func (c *Config) NewClient() (*Client, error) {
} }
// GetRegistrationToken returns a registration token tied with the name of repository and runner. // GetRegistrationToken returns a registration token tied with the name of repository and runner.
func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name string) (*github.RegistrationToken, error) { func (c *Client) GetRegistrationToken(ctx context.Context, enterprise, org, repo, name string) (*github.RegistrationToken, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@@ -89,13 +89,13 @@ func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name strin
return rt, nil return rt, nil
} }
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return rt, err return rt, err
} }
rt, res, err := c.createRegistrationToken(ctx, owner, repo) rt, res, err := c.createRegistrationToken(ctx, enterprise, owner, repo)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create registration token: %v", err) return nil, fmt.Errorf("failed to create registration token: %v", err)
@@ -114,14 +114,14 @@ func (c *Client) GetRegistrationToken(ctx context.Context, org, repo, name strin
} }
// RemoveRunner removes a runner with specified runner ID from repository. // RemoveRunner removes a runner with specified runner ID from repository.
func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID int64) error { func (c *Client) RemoveRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) error {
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return err return err
} }
res, err := c.removeRunner(ctx, owner, repo, runnerID) res, err := c.removeRunner(ctx, enterprise, owner, repo, runnerID)
if err != nil { if err != nil {
return fmt.Errorf("failed to remove runner: %v", err) return fmt.Errorf("failed to remove runner: %v", err)
@@ -135,8 +135,8 @@ func (c *Client) RemoveRunner(ctx context.Context, org, repo string, runnerID in
} }
// ListRunners returns a list of runners of specified owner/repository name. // ListRunners returns a list of runners of specified owner/repository name.
func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.Runner, error) { func (c *Client) ListRunners(ctx context.Context, enterprise, org, repo string) ([]*github.Runner, error) {
owner, repo, err := getOwnerAndRepo(org, repo) enterprise, owner, repo, err := getEnterpriseOrganisationAndRepo(enterprise, org, repo)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -146,7 +146,7 @@ func (c *Client) ListRunners(ctx context.Context, org, repo string) ([]*github.R
opts := github.ListOptions{PerPage: 10} opts := github.ListOptions{PerPage: 10}
for { for {
list, res, err := c.listRunners(ctx, owner, repo, &opts) list, res, err := c.listRunners(ctx, enterprise, owner, repo, &opts)
if err != nil { if err != nil {
return runners, fmt.Errorf("failed to list runners: %v", err) return runners, fmt.Errorf("failed to list runners: %v", err)
@@ -174,42 +174,52 @@ func (c *Client) cleanup() {
} }
} }
// wrappers for github functions (switch between organization/repository mode) // wrappers for github functions (switch between enterprise/organization/repository mode)
// so the calling functions don't need to switch and their code is a bit cleaner // so the calling functions don't need to switch and their code is a bit cleaner
func (c *Client) createRegistrationToken(ctx context.Context, owner, repo string) (*github.RegistrationToken, *github.Response, error) { func (c *Client) createRegistrationToken(ctx context.Context, enterprise, org, repo string) (*github.RegistrationToken, *github.Response, error) {
if len(repo) > 0 { if len(repo) > 0 {
return c.Client.Actions.CreateRegistrationToken(ctx, owner, repo) return c.Client.Actions.CreateRegistrationToken(ctx, org, repo)
}
return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, owner)
}
func (c *Client) removeRunner(ctx context.Context, owner, repo string, runnerID int64) (*github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.RemoveRunner(ctx, owner, repo, runnerID)
}
return c.Client.Actions.RemoveOrganizationRunner(ctx, owner, runnerID)
}
func (c *Client) listRunners(ctx context.Context, owner, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.ListRunners(ctx, owner, repo, opts)
}
return c.Client.Actions.ListOrganizationRunners(ctx, owner, opts)
}
// Validates owner and repo arguments. Both are optional, but at least one should be specified
func getOwnerAndRepo(org, repo string) (string, string, error) {
if len(repo) > 0 {
return splitOwnerAndRepo(repo)
} }
if len(org) > 0 { if len(org) > 0 {
return org, "", nil return c.Client.Actions.CreateOrganizationRegistrationToken(ctx, org)
} }
return "", "", fmt.Errorf("organization and repository are both empty") return c.Client.Enterprise.CreateRegistrationToken(ctx, enterprise)
}
func (c *Client) removeRunner(ctx context.Context, enterprise, org, repo string, runnerID int64) (*github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.RemoveRunner(ctx, org, repo, runnerID)
}
if len(org) > 0 {
return c.Client.Actions.RemoveOrganizationRunner(ctx, org, runnerID)
}
return c.Client.Enterprise.RemoveRunner(ctx, enterprise, runnerID)
}
func (c *Client) listRunners(ctx context.Context, enterprise, org, repo string, opts *github.ListOptions) (*github.Runners, *github.Response, error) {
if len(repo) > 0 {
return c.Client.Actions.ListRunners(ctx, org, repo, opts)
}
if len(org) > 0 {
return c.Client.Actions.ListOrganizationRunners(ctx, org, opts)
}
return c.Client.Enterprise.ListRunners(ctx, enterprise, opts)
}
// Validates enterprise, organisation and repo arguments. Both are optional, but at least one should be specified
func getEnterpriseOrganisationAndRepo(enterprise, org, repo string) (string, string, string, error) {
if len(repo) > 0 {
owner, repository, err := splitOwnerAndRepo(repo)
return "", owner, repository, err
}
if len(org) > 0 {
return "", org, "", nil
}
if len(enterprise) > 0 {
return enterprise, "", "", nil
}
return "", "", "", fmt.Errorf("enterprise, organization and repository are all empty")
} }
func getRegistrationKey(org, repo string) string { func getRegistrationKey(org, repo string) string {

View File

@@ -39,22 +39,26 @@ func TestMain(m *testing.M) {
func TestGetRegistrationToken(t *testing.T) { func TestGetRegistrationToken(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
token string repo string
err bool token string
err bool
}{ }{
{org: "", repo: "test/valid", token: fake.RegistrationToken, err: false}, {enterprise: "", org: "", repo: "test/valid", token: fake.RegistrationToken, err: false},
{org: "", repo: "test/invalid", token: "", err: true}, {enterprise: "", org: "", repo: "test/invalid", token: "", err: true},
{org: "", repo: "test/error", token: "", err: true}, {enterprise: "", org: "", repo: "test/error", token: "", err: true},
{org: "test", repo: "", token: fake.RegistrationToken, err: false}, {enterprise: "", org: "test", repo: "", token: fake.RegistrationToken, err: false},
{org: "invalid", repo: "", token: "", err: true}, {enterprise: "", org: "invalid", repo: "", token: "", err: true},
{org: "error", repo: "", token: "", err: true}, {enterprise: "", org: "error", repo: "", token: "", err: true},
{enterprise: "test", org: "", repo: "", token: fake.RegistrationToken, err: false},
{enterprise: "invalid", org: "", repo: "", token: "", err: true},
{enterprise: "error", org: "", repo: "", token: "", err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
rt, err := client.GetRegistrationToken(context.Background(), tt.org, tt.repo, "test") rt, err := client.GetRegistrationToken(context.Background(), tt.enterprise, tt.org, tt.repo, "test")
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }
@@ -66,22 +70,26 @@ func TestGetRegistrationToken(t *testing.T) {
func TestListRunners(t *testing.T) { func TestListRunners(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
length int repo string
err bool length int
err bool
}{ }{
{org: "", repo: "test/valid", length: 2, err: false}, {enterprise: "", org: "", repo: "test/valid", length: 2, err: false},
{org: "", repo: "test/invalid", length: 0, err: true}, {enterprise: "", org: "", repo: "test/invalid", length: 0, err: true},
{org: "", repo: "test/error", length: 0, err: true}, {enterprise: "", org: "", repo: "test/error", length: 0, err: true},
{org: "test", repo: "", length: 2, err: false}, {enterprise: "", org: "test", repo: "", length: 2, err: false},
{org: "invalid", repo: "", length: 0, err: true}, {enterprise: "", org: "invalid", repo: "", length: 0, err: true},
{org: "error", repo: "", length: 0, err: true}, {enterprise: "", org: "error", repo: "", length: 0, err: true},
{enterprise: "test", org: "", repo: "", length: 2, err: false},
{enterprise: "invalid", org: "", repo: "", length: 0, err: true},
{enterprise: "error", org: "", repo: "", length: 0, err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
runners, err := client.ListRunners(context.Background(), tt.org, tt.repo) runners, err := client.ListRunners(context.Background(), tt.enterprise, tt.org, tt.repo)
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }
@@ -93,21 +101,25 @@ func TestListRunners(t *testing.T) {
func TestRemoveRunner(t *testing.T) { func TestRemoveRunner(t *testing.T) {
tests := []struct { tests := []struct {
org string enterprise string
repo string org string
err bool repo string
err bool
}{ }{
{org: "", repo: "test/valid", err: false}, {enterprise: "", org: "", repo: "test/valid", err: false},
{org: "", repo: "test/invalid", err: true}, {enterprise: "", org: "", repo: "test/invalid", err: true},
{org: "", repo: "test/error", err: true}, {enterprise: "", org: "", repo: "test/error", err: true},
{org: "test", repo: "", err: false}, {enterprise: "", org: "test", repo: "", err: false},
{org: "invalid", repo: "", err: true}, {enterprise: "", org: "invalid", repo: "", err: true},
{org: "error", repo: "", err: true}, {enterprise: "", org: "error", repo: "", err: true},
{enterprise: "test", org: "", repo: "", err: false},
{enterprise: "invalid", org: "", repo: "", err: true},
{enterprise: "error", org: "", repo: "", err: true},
} }
client := newTestClient() client := newTestClient()
for i, tt := range tests { for i, tt := range tests {
err := client.RemoveRunner(context.Background(), tt.org, tt.repo, int64(1)) err := client.RemoveRunner(context.Background(), tt.enterprise, tt.org, tt.repo, int64(1))
if !tt.err && err != nil { if !tt.err && err != nil {
t.Errorf("[%d] unexpected error: %v", i, err) t.Errorf("[%d] unexpected error: %v", i, err)
} }

5
go.mod
View File

@@ -6,10 +6,7 @@ require (
github.com/bradleyfalzon/ghinstallation v1.1.1 github.com/bradleyfalzon/ghinstallation v1.1.1
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/go-logr/logr v0.1.0 github.com/go-logr/logr v0.1.0
github.com/google/go-github v17.0.0+incompatible // indirect github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04
github.com/google/go-github/v33 v33.0.0
github.com/google/go-querystring v1.0.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/onsi/ginkgo v1.8.0 github.com/onsi/ginkgo v1.8.0

8
go.sum
View File

@@ -116,14 +116,10 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04 h1:wEYk2h/GwOhImcVjiTIceP88WxVbXw2F+ARYUQMEsfg= github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a h1:Z9Nzq8ntvvXCLnFGOkzzcD8HDOzOo+obuwE5oK85vNQ=
github.com/google/go-github/v32 v32.1.1-0.20200822031813-d57a3a84ba04/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-github/v33 v33.0.1-0.20210204004227-319dcffb518a/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=

View File

@@ -4,6 +4,8 @@ ARG TARGETPLATFORM
ARG RUNNER_VERSION=2.274.2 ARG RUNNER_VERSION=2.274.2
ARG DOCKER_VERSION=19.03.12 ARG DOCKER_VERSION=19.03.12
RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false)
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt update -y \ RUN apt update -y \
&& apt install -y software-properties-common \ && apt install -y software-properties-common \
@@ -42,7 +44,8 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& chmod +x /usr/local/bin/dumb-init && chmod +x /usr/local/bin/dumb-init
# Docker download supports arm64 as aarch64 & amd64 as x86_64 # Docker download supports arm64 as aarch64 & amd64 as x86_64
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN set -vx; \
export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x86_64 ; fi \
&& curl -L -o docker.tgz https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz \ && curl -L -o docker.tgz https://download.docker.com/linux/static/stable/${ARCH}/docker-${DOCKER_VERSION}.tgz \
@@ -55,6 +58,8 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& usermod -aG docker runner \ && usermod -aG docker runner \
&& echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers && echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" > /etc/sudoers
ENV RUNNER_ASSETS_DIR=/runnertmp
# Runner download supports amd64 as x64. Externalstmp is needed for making mount points work inside DinD. # Runner download supports amd64 as x64. Externalstmp is needed for making mount points work inside DinD.
# #
# libyaml-dev is required for ruby/setup-ruby action. # libyaml-dev is required for ruby/setup-ruby action.
@@ -62,8 +67,8 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
# to avoid rerunning apt-update on its own. # to avoid rerunning apt-update on its own.
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
&& mkdir -p /runner \ && mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd /runner \ && cd "$RUNNER_ASSETS_DIR" \
&& curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \ && tar xzf ./runner.tar.gz \
&& rm runner.tar.gz \ && rm runner.tar.gz \
@@ -72,14 +77,14 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& apt-get install -y libyaml-dev \ && apt-get install -y libyaml-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > /runner.env \ RUN echo AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache > .env \
&& mkdir /opt/hostedtoolcache \ && mkdir /opt/hostedtoolcache \
&& chgrp runner /opt/hostedtoolcache \ && chgrp runner /opt/hostedtoolcache \
&& chmod g+rwx /opt/hostedtoolcache && chmod g+rwx /opt/hostedtoolcache
COPY entrypoint.sh /runner COPY entrypoint.sh /
COPY patched /runner/patched COPY patched $RUNNER_ASSETS_DIR/patched
USER runner USER runner
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"] ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/runner/entrypoint.sh"] CMD ["/entrypoint.sh"]

View File

@@ -23,15 +23,13 @@ else
endif endif
docker-build: docker-build:
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} -t ${NAME}:v${RUNNER_VERSION} . docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${NAME}:${TAG} .
docker build --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -t ${DIND_RUNNER_NAME}:v${RUNNER_VERSION} -f dindrunner.Dockerfile . docker build --build-arg TARGETPLATFORM=amd64 --build-arg RUNNER_VERSION=${RUNNER_VERSION} --build-arg DOCKER_VERSION=${DOCKER_VERSION} -t ${DIND_RUNNER_NAME}:${TAG} -f dindrunner.Dockerfile .
docker-push: docker-push:
docker push ${NAME}:${TAG} docker push ${NAME}:${TAG}
docker push ${NAME}:v${RUNNER_VERSION}
docker push ${DIND_RUNNER_NAME}:${TAG} docker push ${DIND_RUNNER_NAME}:${TAG}
docker push ${DIND_RUNNER_NAME}:v${RUNNER_VERSION}
docker-buildx: docker-buildx:
export DOCKER_CLI_EXPERIMENTAL=enabled export DOCKER_CLI_EXPERIMENTAL=enabled

View File

@@ -48,6 +48,8 @@ ARG DOCKER_CHANNEL=stable
ARG DOCKER_VERSION=19.03.13 ARG DOCKER_VERSION=19.03.13
ARG DEBUG=false ARG DEBUG=false
RUN test -n "$TARGETPLATFORM" || (echo "TARGETPLATFORM must be set" && false)
# Docker installation # Docker installation
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \ && if [ "$ARCH" = "arm64" ]; then export ARCH=aarch64 ; fi \
@@ -66,6 +68,8 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
dockerd --version; \ dockerd --version; \
docker --version docker --version
ENV RUNNER_ASSETS_DIR=/runnertmp
# Runner download supports amd64 as x64 # Runner download supports amd64 as x64
# #
# libyaml-dev is required for ruby/setup-ruby action. # libyaml-dev is required for ruby/setup-ruby action.
@@ -73,8 +77,8 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
# to avoid rerunning apt-update on its own. # to avoid rerunning apt-update on its own.
RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
&& if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \ && if [ "$ARCH" = "amd64" ]; then export ARCH=x64 ; fi \
&& mkdir -p /runner \ && mkdir -p "$RUNNER_ASSETS_DIR" \
&& cd /runner \ && cd "$RUNNER_ASSETS_DIR" \
&& curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \ && curl -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \ && tar xzf ./runner.tar.gz \
&& rm runner.tar.gz \ && rm runner.tar.gz \
@@ -100,7 +104,7 @@ RUN export ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) \
VOLUME /var/lib/docker VOLUME /var/lib/docker
COPY patched /runner/patched COPY patched $RUNNER_ASSETS_DIR/patched
# No group definition, as that makes it harder to run docker. # No group definition, as that makes it harder to run docker.
USER runner USER runner

View File

@@ -16,14 +16,16 @@ if [ -z "${RUNNER_NAME}" ]; then
exit 1 exit 1
fi fi
if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ]; then if [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_REPO}" ] && [ -n "${RUNNER_ENTERPRISE}" ]; then
ATTACH="${RUNNER_ORG}/${RUNNER_REPO}" ATTACH="${RUNNER_ORG}/${RUNNER_REPO}"
elif [ -n "${RUNNER_ORG}" ]; then elif [ -n "${RUNNER_ORG}" ]; then
ATTACH="${RUNNER_ORG}" ATTACH="${RUNNER_ORG}"
elif [ -n "${RUNNER_REPO}" ]; then elif [ -n "${RUNNER_REPO}" ]; then
ATTACH="${RUNNER_REPO}" ATTACH="${RUNNER_REPO}"
elif [ -n "${RUNNER_ENTERPRISE}" ]; then
ATTACH="enterprises/${RUNNER_ENTERPRISE}"
else else
echo "At least one of RUNNER_ORG or RUNNER_REPO must be set" 1>&2 echo "At least one of RUNNER_ORG or RUNNER_REPO or RUNNER_ENTERPRISE must be set" 1>&2
exit 1 exit 1
fi fi
@@ -44,9 +46,18 @@ if [ -z "${RUNNER_REPO}" ] && [ -n "${RUNNER_ORG}" ] && [ -n "${RUNNER_GROUP}" ]
RUNNER_GROUP_ARG="--runnergroup ${RUNNER_GROUP}" RUNNER_GROUP_ARG="--runnergroup ${RUNNER_GROUP}"
fi fi
# Hack due to https://github.com/summerwind/actions-runner-controller/issues/252#issuecomment-758338483
if [ ! -d /runner ]; then
echo "/runner should be an emptyDir mount. Please fix the pod spec." 1>&2
exit 1
fi
sudo chown -R runner:docker /runner
mv /runnertmp/* /runner/
cd /runner cd /runner
./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "${GITHUB_URL}${ATTACH}" --token "${RUNNER_TOKEN}" ${RUNNER_GROUP_ARG} ${LABEL_ARG} ${WORKDIR_ARG} ./config.sh --unattended --replace --name "${RUNNER_NAME}" --url "${GITHUB_URL}${ATTACH}" --token "${RUNNER_TOKEN}" ${RUNNER_GROUP_ARG} ${LABEL_ARG} ${WORKDIR_ARG}
mkdir ./externals
# Hack due to the DinD volumes # Hack due to the DinD volumes
mv ./externalstmp/* ./externals/ mv ./externalstmp/* ./externals/