mirror of
https://github.com/actions/actions-runner-controller.git
synced 2025-12-10 11:41:27 +00:00
#2490 has been happening since v0.27.2 for non-dind runners based on Ubuntu 20.04 runner images. It does not affect Ubuntu 22.04 non-dind runners(i.e. runners with dockerd sidecars) and Ubuntu 20.04/22.04 dind runners(i.e. runners without dockerd sidecars). However, presuming many folks are still using Ubuntu 20.04 runners and non-dind runners, we should fix it. This change tries to fix it by defaulting to the docker group id 1001 used by Ubuntu 20.04 runners, and use gid 121 for Ubuntu 22.04 runners. We use the image tag to see which Ubuntu version the runner is based on. The algorithm is so simple- we assume it's Ubuntu-22.04-based if the image tag contains "22.04". This might be a breaking change for folks who have already upgraded to Ubuntu 22.04 runners using their own custom runner images. Note again; we rely on the image tag to detect Ubuntu 22.04 runner images and use the proper docker gid- Folks using our official Ubuntu 22.04 runner images are not affected. It is a breaking change anyway, so I have added a remedy- ARC got a new flag, `--docker-gid`, which defaults to `1001` but can be set to `121` or whatever gid the operator/admin likes. This can be set to `--docker-gid=121`, for example, if you are using your own custom runner image based on Ubuntu 22.04 and the image tag does not contain "22.04". Fixes #2490
1276 lines
30 KiB
Go
1276 lines
30 KiB
Go
package actionssummerwindnet
|
|
|
|
import (
|
|
"testing"
|
|
|
|
arcv1alpha1 "github.com/actions/actions-runner-controller/apis/actions.summerwind.net/v1alpha1"
|
|
"github.com/actions/actions-runner-controller/github"
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
func newRunnerPod(template corev1.Pod, runnerSpec arcv1alpha1.RunnerConfig, githubBaseURL string, d RunnerPodDefaults) (corev1.Pod, error) {
|
|
return newRunnerPodWithContainerMode("", template, runnerSpec, githubBaseURL, d)
|
|
}
|
|
|
|
func setEnv(c *corev1.Container, name, value string) {
|
|
for j := range c.Env {
|
|
e := &c.Env[j]
|
|
|
|
if e.Name == name {
|
|
e.Value = value
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func newWorkGenericEphemeralVolume(t *testing.T, storageReq string) corev1.Volume {
|
|
GBs, err := resource.ParseQuantity(storageReq)
|
|
if err != nil {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
|
|
return corev1.Volume{
|
|
Name: "work",
|
|
VolumeSource: corev1.VolumeSource{
|
|
Ephemeral: &corev1.EphemeralVolumeSource{
|
|
VolumeClaimTemplate: &corev1.PersistentVolumeClaimTemplate{
|
|
Spec: corev1.PersistentVolumeClaimSpec{
|
|
AccessModes: []corev1.PersistentVolumeAccessMode{
|
|
corev1.ReadWriteOnce,
|
|
},
|
|
StorageClassName: strPtr("runner-work-dir"),
|
|
Resources: corev1.ResourceRequirements{
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceStorage: GBs,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestNewRunnerPod(t *testing.T) {
|
|
workGenericEphemeralVolume := newWorkGenericEphemeralVolume(t, "10Gi")
|
|
|
|
type testcase struct {
|
|
description string
|
|
|
|
template corev1.Pod
|
|
config arcv1alpha1.RunnerConfig
|
|
want corev1.Pod
|
|
}
|
|
|
|
base := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"actions-runner": "",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "work",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
{
|
|
Name: "DOCKER_HOST",
|
|
Value: "unix:///run/docker/docker.sock",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{},
|
|
},
|
|
{
|
|
Name: "docker",
|
|
Image: "default-docker-image",
|
|
Args: []string{
|
|
"dockerd",
|
|
"--host=unix:///run/docker/docker.sock",
|
|
"--group=$(DOCKER_GROUP_GID)",
|
|
},
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "DOCKER_GROUP_GID",
|
|
Value: "1234",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
},
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: func(b bool) *bool { return &b }(true),
|
|
},
|
|
Lifecycle: &corev1.Lifecycle{
|
|
PreStop: &corev1.LifecycleHandler{
|
|
Exec: &corev1.ExecAction{
|
|
Command: []string{
|
|
"/bin/sh",
|
|
"-c",
|
|
"timeout \"${RUNNER_GRACEFUL_STOP_TIMEOUT:-15}\" /bin/sh -c \"echo 'Prestop hook started'; while [ -f /runner/.runner ]; do sleep 1; done; echo 'Waiting for dockerd to start'; while ! pgrep -x dockerd; do sleep 1; done; echo 'Prestop hook stopped'\" >/proc/1/fd/1 2>&1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
boolPtr := func(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
dinrBase := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"actions-runner": "",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
dockerDisabled := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"actions-runner": "",
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
newTestPod := func(base corev1.Pod, f func(*corev1.Pod)) corev1.Pod {
|
|
pod := base.DeepCopy()
|
|
if f != nil {
|
|
f(pod)
|
|
}
|
|
return *pod
|
|
}
|
|
|
|
testcases := []testcase{
|
|
{
|
|
description: "it should have unprivileged runner and privileged sidecar docker container",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{},
|
|
want: newTestPod(base, nil),
|
|
},
|
|
{
|
|
description: "it should respect DOCKER_GROUP_GID of the dockerd sidecar container",
|
|
template: corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "docker",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "DOCKER_GROUP_GID",
|
|
Value: "2345",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
config: arcv1alpha1.RunnerConfig{},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
setEnv(&p.Spec.Containers[1], "DOCKER_GROUP_GID", "2345")
|
|
}),
|
|
},
|
|
{
|
|
description: "it should add DOCKER_GROUP_GID=1001 to the dockerd sidecar container for Ubuntu 20.04 runners",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
Image: "ghcr.io/summerwind/actions-runner:ubuntu-20.04-20210726-1",
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
setEnv(&p.Spec.Containers[1], "DOCKER_GROUP_GID", "1001")
|
|
p.Spec.Containers[0].Image = "ghcr.io/summerwind/actions-runner:ubuntu-20.04-20210726-1"
|
|
}),
|
|
},
|
|
{
|
|
description: "it should add DOCKER_GROUP_GID=121 to the dockerd sidecar container for Ubuntu 22.04 runners",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
Image: "ghcr.io/summerwind/actions-runner:ubuntu-22.04-20210726-1",
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
setEnv(&p.Spec.Containers[1], "DOCKER_GROUP_GID", "121")
|
|
p.Spec.Containers[0].Image = "ghcr.io/summerwind/actions-runner:ubuntu-22.04-20210726-1"
|
|
}),
|
|
},
|
|
{
|
|
description: "dockerdWithinRunnerContainer=true should set privileged=true and omit the dind sidecar container",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
},
|
|
want: newTestPod(dinrBase, nil),
|
|
},
|
|
{
|
|
description: "in the default config you should provide both dockerdWithinRunnerContainer=true and runnerImage",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
Image: "dind-runner-image",
|
|
},
|
|
want: newTestPod(dinrBase, func(p *corev1.Pod) {
|
|
p.Spec.Containers[0].Image = "dind-runner-image"
|
|
}),
|
|
},
|
|
{
|
|
description: "dockerEnabled=false should have no effect when dockerdWithinRunnerContainer=true",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
want: newTestPod(dinrBase, nil),
|
|
},
|
|
{
|
|
description: "dockerEnabled=false should omit the dind sidecar and set privileged=false and envvars DOCKER_ENABLED=false and DOCKERD_IN_RUNNER=false",
|
|
template: corev1.Pod{},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
want: newTestPod(dockerDisabled, nil),
|
|
},
|
|
{
|
|
description: "TODO: dockerEnabled=false results in privileged=false by default but you can override it",
|
|
template: corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
config: arcv1alpha1.RunnerConfig{
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
want: newTestPod(dockerDisabled, func(p *corev1.Pod) {
|
|
p.Spec.Containers[0].SecurityContext.Privileged = boolPtr(true)
|
|
}),
|
|
},
|
|
{
|
|
description: "Mount generic ephemeral volume onto work (with explicit volumeMount)",
|
|
template: corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Volumes: []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
p.Spec.Volumes = []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
p.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "Mount generic ephemeral volume onto work (without explicit volumeMount)",
|
|
template: corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
p.Spec.Volumes = []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}),
|
|
},
|
|
}
|
|
|
|
var (
|
|
defaultRunnerImage = "default-runner-image"
|
|
defaultRunnerImagePullSecrets = []string{}
|
|
defaultDockerImage = "default-docker-image"
|
|
defaultDockerRegistryMirror = ""
|
|
githubBaseURL = "api.github.com"
|
|
)
|
|
|
|
for i := range testcases {
|
|
tc := testcases[i]
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
got, err := newRunnerPod(tc.template, tc.config, githubBaseURL, RunnerPodDefaults{
|
|
RunnerImage: defaultRunnerImage,
|
|
RunnerImagePullSecrets: defaultRunnerImagePullSecrets,
|
|
DockerImage: defaultDockerImage,
|
|
DockerRegistryMirror: defaultDockerRegistryMirror,
|
|
DockerGID: "1234",
|
|
UseRunnerStatusUpdateHook: false,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func strPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
|
workGenericEphemeralVolume := newWorkGenericEphemeralVolume(t, "10Gi")
|
|
|
|
type testcase struct {
|
|
description string
|
|
|
|
runner arcv1alpha1.Runner
|
|
want corev1.Pod
|
|
}
|
|
|
|
boolPtr := func(v bool) *bool {
|
|
return &v
|
|
}
|
|
|
|
base := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"pod-template-hash": "8857b86c7",
|
|
"actions-runner": "",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "actions.summerwind.dev/v1alpha1",
|
|
Kind: "Runner",
|
|
Name: "runner",
|
|
Controller: boolPtr(true),
|
|
BlockOwnerDeletion: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "work",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
{
|
|
Name: "DOCKER_HOST",
|
|
Value: "unix:///run/docker/docker.sock",
|
|
},
|
|
{
|
|
Name: "RUNNER_NAME",
|
|
Value: "runner",
|
|
},
|
|
{
|
|
Name: "RUNNER_TOKEN",
|
|
Value: "",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{},
|
|
},
|
|
{
|
|
Name: "docker",
|
|
Image: "default-docker-image",
|
|
Args: []string{
|
|
"dockerd",
|
|
"--host=unix:///run/docker/docker.sock",
|
|
"--group=$(DOCKER_GROUP_GID)",
|
|
},
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "DOCKER_GROUP_GID",
|
|
Value: "1234",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
},
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: func(b bool) *bool { return &b }(true),
|
|
},
|
|
Lifecycle: &corev1.Lifecycle{
|
|
PreStop: &corev1.LifecycleHandler{
|
|
Exec: &corev1.ExecAction{
|
|
Command: []string{
|
|
"/bin/sh",
|
|
"-c",
|
|
"timeout \"${RUNNER_GRACEFUL_STOP_TIMEOUT:-15}\" /bin/sh -c \"echo 'Prestop hook started'; while [ -f /runner/.runner ]; do sleep 1; done; echo 'Waiting for dockerd to start'; while ! pgrep -x dockerd; do sleep 1; done; echo 'Prestop hook stopped'\" >/proc/1/fd/1 2>&1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
dinrBase := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"pod-template-hash": "8857b86c7",
|
|
"actions-runner": "",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "actions.summerwind.dev/v1alpha1",
|
|
Kind: "Runner",
|
|
Name: "runner",
|
|
Controller: boolPtr(true),
|
|
BlockOwnerDeletion: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
{
|
|
Name: "RUNNER_NAME",
|
|
Value: "runner",
|
|
},
|
|
{
|
|
Name: "RUNNER_TOKEN",
|
|
Value: "",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
dockerDisabled := corev1.Pod{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
Labels: map[string]string{
|
|
"actions-runner-controller/inject-registration-token": "true",
|
|
"pod-template-hash": "8857b86c7",
|
|
"actions-runner": "",
|
|
},
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: "actions.summerwind.dev/v1alpha1",
|
|
Kind: "Runner",
|
|
Name: "runner",
|
|
Controller: boolPtr(true),
|
|
BlockOwnerDeletion: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "default-runner-image",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "RUNNER_ORG",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_REPO",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_ENTERPRISE",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_LABELS",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "RUNNER_GROUP",
|
|
Value: "",
|
|
},
|
|
{
|
|
Name: "DOCKER_ENABLED",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "DOCKERD_IN_RUNNER",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_URL",
|
|
Value: "api.github.com",
|
|
},
|
|
{
|
|
Name: "RUNNER_WORKDIR",
|
|
Value: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "RUNNER_EPHEMERAL",
|
|
Value: "true",
|
|
},
|
|
{
|
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
|
Value: "false",
|
|
},
|
|
{
|
|
Name: "GITHUB_ACTIONS_RUNNER_EXTRA_USER_AGENT",
|
|
Value: "actions-runner-controller/NA",
|
|
},
|
|
{
|
|
Name: "RUNNER_NAME",
|
|
Value: "runner",
|
|
},
|
|
{
|
|
Name: "RUNNER_TOKEN",
|
|
Value: "",
|
|
},
|
|
},
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
},
|
|
ImagePullPolicy: corev1.PullAlways,
|
|
SecurityContext: &corev1.SecurityContext{},
|
|
},
|
|
},
|
|
RestartPolicy: corev1.RestartPolicyNever,
|
|
},
|
|
}
|
|
|
|
newTestPod := func(base corev1.Pod, f func(*corev1.Pod)) corev1.Pod {
|
|
pod := base.DeepCopy()
|
|
if f != nil {
|
|
f(pod)
|
|
}
|
|
return *pod
|
|
}
|
|
|
|
testcases := []testcase{
|
|
{
|
|
description: "it should have unprivileged runner and privileged sidecar docker container",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{},
|
|
},
|
|
},
|
|
want: newTestPod(base, nil),
|
|
},
|
|
{
|
|
description: "dockerdWithinRunnerContainer=true should set privileged=true and omit the dind sidecar container",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(dinrBase, nil),
|
|
},
|
|
{
|
|
description: "in the default config you should provide both dockerdWithinRunnerContainer=true and runnerImage",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
Image: "dind-runner-image",
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(dinrBase, func(p *corev1.Pod) {
|
|
p.Spec.Containers[0].Image = "dind-runner-image"
|
|
}),
|
|
},
|
|
{
|
|
description: "dockerEnabled=false should have no effect when dockerdWithinRunnerContainer=true",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{
|
|
DockerdWithinRunnerContainer: boolPtr(true),
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(dinrBase, nil),
|
|
},
|
|
{
|
|
description: "dockerEnabled=false should omit the dind sidecar and set privileged=false and envvars DOCKER_ENABLED=false and DOCKERD_IN_RUNNER=false",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(dockerDisabled, nil),
|
|
},
|
|
{
|
|
description: "TODO: dockerEnabled=false results in privileged=false by default but you can override it",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerConfig: arcv1alpha1.RunnerConfig{
|
|
DockerEnabled: boolPtr(false),
|
|
},
|
|
RunnerPodSpec: arcv1alpha1.RunnerPodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: boolPtr(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
want: newTestPod(dockerDisabled, func(p *corev1.Pod) {
|
|
p.Spec.Containers[0].SecurityContext.Privileged = boolPtr(true)
|
|
}),
|
|
},
|
|
{
|
|
description: "Mount generic ephemeral volume onto work (with explicit volumeMount)",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerPodSpec: arcv1alpha1.RunnerPodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Volumes: []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
p.Spec.Volumes = []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
workGenericEphemeralVolume,
|
|
}
|
|
p.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
|
{
|
|
Name: "work",
|
|
MountPath: "/runner/_work",
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
MountPath: "/run/docker",
|
|
},
|
|
{
|
|
Name: "runner",
|
|
MountPath: "/runner",
|
|
},
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "Mount generic ephemeral volume onto work (without explicit volumeMount)",
|
|
runner: arcv1alpha1.Runner{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "runner",
|
|
},
|
|
Spec: arcv1alpha1.RunnerSpec{
|
|
RunnerPodSpec: arcv1alpha1.RunnerPodSpec{
|
|
Volumes: []corev1.Volume{
|
|
workGenericEphemeralVolume,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: newTestPod(base, func(p *corev1.Pod) {
|
|
p.Spec.Volumes = []corev1.Volume{
|
|
{
|
|
Name: "runner",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{},
|
|
},
|
|
},
|
|
{
|
|
Name: "docker-sock",
|
|
VolumeSource: corev1.VolumeSource{
|
|
EmptyDir: &corev1.EmptyDirVolumeSource{
|
|
Medium: corev1.StorageMediumMemory,
|
|
SizeLimit: resource.NewScaledQuantity(1, resource.Mega),
|
|
},
|
|
},
|
|
},
|
|
workGenericEphemeralVolume,
|
|
}
|
|
}),
|
|
},
|
|
}
|
|
|
|
var (
|
|
defaultRunnerImage = "default-runner-image"
|
|
defaultRunnerImagePullSecrets = []string{}
|
|
defaultDockerImage = "default-docker-image"
|
|
defaultDockerGID = "1234"
|
|
defaultDockerRegistryMirror = ""
|
|
githubBaseURL = "api.github.com"
|
|
)
|
|
|
|
scheme := runtime.NewScheme()
|
|
_ = clientgoscheme.AddToScheme(scheme)
|
|
_ = arcv1alpha1.AddToScheme(scheme)
|
|
|
|
for i := range testcases {
|
|
tc := testcases[i]
|
|
|
|
rr := &testResourceReader{
|
|
objects: map[types.NamespacedName]client.Object{},
|
|
}
|
|
|
|
multiClient := NewMultiGitHubClient(rr, &github.Client{GithubBaseURL: githubBaseURL})
|
|
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
r := &RunnerReconciler{
|
|
GitHubClient: multiClient,
|
|
Scheme: scheme,
|
|
RunnerPodDefaults: RunnerPodDefaults{
|
|
RunnerImage: defaultRunnerImage,
|
|
RunnerImagePullSecrets: defaultRunnerImagePullSecrets,
|
|
DockerImage: defaultDockerImage,
|
|
DockerRegistryMirror: defaultDockerRegistryMirror,
|
|
DockerGID: defaultDockerGID,
|
|
},
|
|
}
|
|
got, err := r.newPod(tc.runner)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|