feat: allow to discover runner statuses (#1268)
* feat: allow to discover runner statuses * fix manifests * Bump runner version to 2.289.1 which includes the hooks support * Add feedback from review * Update reference to newRunnerPod * Fix TestNewRunnerPodFromRunnerController and make hooks file names job specific * Fix additional TestNewRunnerPod test * Cover additional feedback from review * fix rbac manager role * Add permissions to service account for container mode if not provided * Rename flag to runner.statusUpdateHook.enabled and fix needsServiceAccount Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
This commit is contained in:
parent
10b88bf070
commit
11cb9b7882
|
|
@ -183,11 +183,6 @@ func (rs *RunnerSpec) Validate(rootPath *field.Path) field.ErrorList {
|
||||||
errList = append(errList, field.Invalid(rootPath.Child("workVolumeClaimTemplate"), rs.WorkVolumeClaimTemplate, err.Error()))
|
errList = append(errList, field.Invalid(rootPath.Child("workVolumeClaimTemplate"), rs.WorkVolumeClaimTemplate, err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rs.validateIsServiceAccountNameSet()
|
|
||||||
if err != nil {
|
|
||||||
errList = append(errList, field.Invalid(rootPath.Child("serviceAccountName"), rs.ServiceAccountName, err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return errList
|
return errList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,17 +221,6 @@ func (rs *RunnerSpec) validateWorkVolumeClaimTemplate() error {
|
||||||
return rs.WorkVolumeClaimTemplate.validate()
|
return rs.WorkVolumeClaimTemplate.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *RunnerSpec) validateIsServiceAccountNameSet() error {
|
|
||||||
if rs.ContainerMode != "kubernetes" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.ServiceAccountName == "" {
|
|
||||||
return errors.New("service account name is required if container mode is kubernetes")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunnerStatus defines the observed state of Runner
|
// RunnerStatus defines the observed state of Runner
|
||||||
type RunnerStatus struct {
|
type RunnerStatus struct {
|
||||||
// Turns true only if the runner pod is ready.
|
// Turns true only if the runner pod is ready.
|
||||||
|
|
@ -317,6 +301,7 @@ func (w *WorkVolumeClaimTemplate) V1VolumeMount(mountPath string) corev1.VolumeM
|
||||||
// +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
|
||||||
// +kubebuilder:printcolumn:JSONPath=".status.phase",name=Status,type=string
|
// +kubebuilder:printcolumn:JSONPath=".status.phase",name=Status,type=string
|
||||||
|
// +kubebuilder:printcolumn:JSONPath=".status.message",name=Message,type=string
|
||||||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
|
||||||
|
|
||||||
// Runner is the Schema for the runners API
|
// Runner is the Schema for the runners API
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ All additional docs are kept in the `docs/` folder, this README is solely for do
|
||||||
| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). |
|
| `scope.watchNamespace` | Tells the controller and the github webhook server which namespace to watch if `scope.singleNamespace` is true | `Release.Namespace` (the default namespace of the helm chart). |
|
||||||
| `scope.singleNamespace` | Limit the controller to watch a single namespace | false |
|
| `scope.singleNamespace` | Limit the controller to watch a single namespace | false |
|
||||||
| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true |
|
| `certManagerEnabled` | Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true |
|
||||||
|
| `runner.statusUpdateHook.enabled` | Use custom RBAC for runners (role, role binding and service account), this will enable reporting runner statuses | false |
|
||||||
| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | |
|
| `admissionWebHooks.caBundle` | Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | |
|
||||||
| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | |
|
| `githubWebhookServer.logLevel` | Set the log level of the githubWebhookServer container | |
|
||||||
| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 |
|
| `githubWebhookServer.replicaCount` | Set the number of webhook server pods | 1 |
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ spec:
|
||||||
- jsonPath: .status.phase
|
- jsonPath: .status.phase
|
||||||
name: Status
|
name: Status
|
||||||
type: string
|
type: string
|
||||||
|
- jsonPath: .status.message
|
||||||
|
name: Message
|
||||||
|
type: string
|
||||||
- jsonPath: .metadata.creationTimestamp
|
- jsonPath: .metadata.creationTimestamp
|
||||||
name: Age
|
name: Age
|
||||||
type: date
|
type: date
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ spec:
|
||||||
{{- if .Values.runnerGithubURL }}
|
{{- if .Values.runnerGithubURL }}
|
||||||
- "--runner-github-url={{ .Values.runnerGithubURL }}"
|
- "--runner-github-url={{ .Values.runnerGithubURL }}"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.runner.statusUpdateHook.enabled }}
|
||||||
|
- "--runner-status-update-hook"
|
||||||
|
{{- end }}
|
||||||
command:
|
command:
|
||||||
- "/manager"
|
- "/manager"
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -258,3 +258,29 @@ rules:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
{{- if .Values.runner.statusUpdateHook.enabled }}
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- rolebindings
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- roles
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
{{- end }}
|
||||||
|
|
@ -67,6 +67,10 @@ imagePullSecrets: []
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
runner:
|
||||||
|
statusUpdateHook:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
serviceAccount:
|
serviceAccount:
|
||||||
# Specifies whether a service account should be created
|
# Specifies whether a service account should be created
|
||||||
create: true
|
create: true
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ spec:
|
||||||
- jsonPath: .status.phase
|
- jsonPath: .status.phase
|
||||||
name: Status
|
name: Status
|
||||||
type: string
|
type: string
|
||||||
|
- jsonPath: .status.message
|
||||||
|
name: Message
|
||||||
|
type: string
|
||||||
- jsonPath: .metadata.creationTimestamp
|
- jsonPath: .metadata.creationTimestamp
|
||||||
name: Age
|
name: Age
|
||||||
type: date
|
type: date
|
||||||
|
|
|
||||||
|
|
@ -258,3 +258,27 @@ rules:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- serviceaccounts
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- rolebindings
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- rbac.authorization.k8s.io
|
||||||
|
resources:
|
||||||
|
- roles
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- delete
|
||||||
|
- get
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "DOCKER_HOST",
|
Name: "DOCKER_HOST",
|
||||||
Value: "tcp://localhost:2376",
|
Value: "tcp://localhost:2376",
|
||||||
|
|
@ -255,6 +259,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
|
|
@ -333,6 +341,10 @@ func TestNewRunnerPod(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{
|
{
|
||||||
|
|
@ -515,7 +527,7 @@ func TestNewRunnerPod(t *testing.T) {
|
||||||
for i := range testcases {
|
for i := range testcases {
|
||||||
tc := testcases[i]
|
tc := testcases[i]
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
got, err := newRunnerPod(tc.template, tc.config, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL)
|
got, err := newRunnerPod(tc.template, tc.config, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.want, got)
|
require.Equal(t, tc.want, got)
|
||||||
})
|
})
|
||||||
|
|
@ -624,6 +636,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "DOCKER_HOST",
|
Name: "DOCKER_HOST",
|
||||||
Value: "tcp://localhost:2376",
|
Value: "tcp://localhost:2376",
|
||||||
|
|
@ -769,6 +785,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "RUNNER_NAME",
|
Name: "RUNNER_NAME",
|
||||||
Value: "runner",
|
Value: "runner",
|
||||||
|
|
@ -866,6 +886,10 @@ func TestNewRunnerPodFromRunnerController(t *testing.T) {
|
||||||
Name: "RUNNER_EPHEMERAL",
|
Name: "RUNNER_EPHEMERAL",
|
||||||
Value: "true",
|
Value: "true",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: "false",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "RUNNER_NAME",
|
Name: "RUNNER_NAME",
|
||||||
Value: "runner",
|
Value: "runner",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -35,6 +36,7 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
|
||||||
|
|
@ -70,7 +72,7 @@ type RunnerReconciler struct {
|
||||||
Name string
|
Name string
|
||||||
RegistrationRecheckInterval time.Duration
|
RegistrationRecheckInterval time.Duration
|
||||||
RegistrationRecheckJitter time.Duration
|
RegistrationRecheckJitter time.Duration
|
||||||
|
UseRunnerStatusUpdateHook bool
|
||||||
UnregistrationRetryDelay time.Duration
|
UnregistrationRetryDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,6 +83,9 @@ type RunnerReconciler struct {
|
||||||
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;delete
|
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;delete
|
||||||
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=get;list;watch;create;update;patch;delete
|
||||||
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
|
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
|
||||||
|
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=create;delete;get
|
||||||
|
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=create;delete;get
|
||||||
|
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=create;delete;get
|
||||||
|
|
||||||
func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||||
log := r.Log.WithValues("runner", req.NamespacedName)
|
log := r.Log.WithValues("runner", req.NamespacedName)
|
||||||
|
|
@ -135,7 +140,7 @@ func (r *RunnerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
|
||||||
|
|
||||||
ready := runnerPodReady(&pod)
|
ready := runnerPodReady(&pod)
|
||||||
|
|
||||||
if runner.Status.Phase != phase || runner.Status.Ready != ready {
|
if (runner.Status.Phase != phase || runner.Status.Ready != ready) && !r.UseRunnerStatusUpdateHook || runner.Status.Phase == "" && r.UseRunnerStatusUpdateHook {
|
||||||
if pod.Status.Phase == corev1.PodRunning {
|
if pod.Status.Phase == corev1.PodRunning {
|
||||||
// Seeing this message, you can expect the runner to become `Running` soon.
|
// Seeing this message, you can expect the runner to become `Running` soon.
|
||||||
log.V(1).Info(
|
log.V(1).Info(
|
||||||
|
|
@ -256,6 +261,91 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needsServiceAccount := runner.Spec.ServiceAccountName == "" && (r.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes")
|
||||||
|
if needsServiceAccount {
|
||||||
|
serviceAccount := &corev1.ServiceAccount{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: runner.ObjectMeta.Name,
|
||||||
|
Namespace: runner.ObjectMeta.Namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if res := r.createObject(ctx, serviceAccount, serviceAccount.ObjectMeta, &runner, log); res != nil {
|
||||||
|
return *res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rules := []rbacv1.PolicyRule{}
|
||||||
|
|
||||||
|
if r.UseRunnerStatusUpdateHook {
|
||||||
|
rules = append(rules, []rbacv1.PolicyRule{
|
||||||
|
{
|
||||||
|
APIGroups: []string{"actions.summerwind.dev"},
|
||||||
|
Resources: []string{"runners/status"},
|
||||||
|
Verbs: []string{"get", "update", "patch"},
|
||||||
|
ResourceNames: []string{runner.ObjectMeta.Name},
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if runner.Spec.ContainerMode == "kubernetes" {
|
||||||
|
// Permissions based on https://github.com/actions/runner-container-hooks/blob/main/packages/k8s/README.md
|
||||||
|
rules = append(rules, []rbacv1.PolicyRule{
|
||||||
|
{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
Resources: []string{"pods"},
|
||||||
|
Verbs: []string{"get", "list", "create", "delete"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
Resources: []string{"pods/exec"},
|
||||||
|
Verbs: []string{"get", "create"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
Resources: []string{"pods/log"},
|
||||||
|
Verbs: []string{"get", "list", "watch"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
APIGroups: []string{""},
|
||||||
|
Resources: []string{"secrets"},
|
||||||
|
Verbs: []string{"get", "list", "create", "delete"},
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
role := &rbacv1.Role{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: runner.ObjectMeta.Name,
|
||||||
|
Namespace: runner.ObjectMeta.Namespace,
|
||||||
|
},
|
||||||
|
Rules: rules,
|
||||||
|
}
|
||||||
|
if res := r.createObject(ctx, role, role.ObjectMeta, &runner, log); res != nil {
|
||||||
|
return *res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
roleBinding := &rbacv1.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: runner.ObjectMeta.Name,
|
||||||
|
Namespace: runner.ObjectMeta.Namespace,
|
||||||
|
},
|
||||||
|
RoleRef: rbacv1.RoleRef{
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
Kind: "Role",
|
||||||
|
Name: runner.ObjectMeta.Name,
|
||||||
|
},
|
||||||
|
Subjects: []rbacv1.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: runner.ObjectMeta.Name,
|
||||||
|
Namespace: runner.ObjectMeta.Namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if res := r.createObject(ctx, roleBinding, roleBinding.ObjectMeta, &runner, log); res != nil {
|
||||||
|
return *res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.Create(ctx, &newPod); err != nil {
|
if err := r.Create(ctx, &newPod); err != nil {
|
||||||
if kerrors.IsAlreadyExists(err) {
|
if kerrors.IsAlreadyExists(err) {
|
||||||
// Gracefully handle pod-already-exists errors due to informer cache delay.
|
// Gracefully handle pod-already-exists errors due to informer cache delay.
|
||||||
|
|
@ -278,6 +368,27 @@ func (r *RunnerReconciler) processRunnerCreation(ctx context.Context, runner v1a
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RunnerReconciler) createObject(ctx context.Context, obj client.Object, meta metav1.ObjectMeta, runner *v1alpha1.Runner, log logr.Logger) *ctrl.Result {
|
||||||
|
kind := strings.Split(reflect.TypeOf(obj).String(), ".")[1]
|
||||||
|
if err := ctrl.SetControllerReference(runner, obj, r.Scheme); err != nil {
|
||||||
|
log.Error(err, fmt.Sprintf("Could not add owner reference to %s %s. %s", kind, meta.Name, err.Error()))
|
||||||
|
return &ctrl.Result{Requeue: true}
|
||||||
|
}
|
||||||
|
if err := r.Create(ctx, obj); err != nil {
|
||||||
|
if kerrors.IsAlreadyExists(err) {
|
||||||
|
log.Info(fmt.Sprintf("Failed to create %s %s as it already exists. Reusing existing %s", kind, meta.Name, kind))
|
||||||
|
r.Recorder.Event(runner, corev1.EventTypeNormal, fmt.Sprintf("%sReused", kind), fmt.Sprintf("Reused %s '%s'", kind, meta.Name))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error(err, fmt.Sprintf("Retrying as failed to create %s %s resource", kind, meta.Name))
|
||||||
|
return &ctrl.Result{Requeue: true}
|
||||||
|
}
|
||||||
|
r.Recorder.Event(runner, corev1.EventTypeNormal, fmt.Sprintf("%sCreated", kind), fmt.Sprintf("Created %s '%s'", kind, meta.Name))
|
||||||
|
log.Info(fmt.Sprintf("Created %s", kind), "name", meta.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v1alpha1.Runner) (bool, error) {
|
func (r *RunnerReconciler) updateRegistrationToken(ctx context.Context, runner v1alpha1.Runner) (bool, error) {
|
||||||
if runner.IsRegisterable() {
|
if runner.IsRegisterable() {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
@ -426,7 +537,7 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pod, err := newRunnerPodWithContainerMode(runner.Spec.ContainerMode, template, runner.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, r.GitHubClient.GithubBaseURL)
|
pod, err := newRunnerPodWithContainerMode(runner.Spec.ContainerMode, template, runner.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, r.GitHubClient.GithubBaseURL, r.UseRunnerStatusUpdateHook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pod, err
|
return pod, err
|
||||||
}
|
}
|
||||||
|
|
@ -474,9 +585,13 @@ func (r *RunnerReconciler) newPod(runner v1alpha1.Runner) (corev1.Pod, error) {
|
||||||
if runnerSpec.NodeSelector != nil {
|
if runnerSpec.NodeSelector != nil {
|
||||||
pod.Spec.NodeSelector = runnerSpec.NodeSelector
|
pod.Spec.NodeSelector = runnerSpec.NodeSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
if runnerSpec.ServiceAccountName != "" {
|
if runnerSpec.ServiceAccountName != "" {
|
||||||
pod.Spec.ServiceAccountName = runnerSpec.ServiceAccountName
|
pod.Spec.ServiceAccountName = runnerSpec.ServiceAccountName
|
||||||
|
} else if r.UseRunnerStatusUpdateHook || runner.Spec.ContainerMode == "kubernetes" {
|
||||||
|
pod.Spec.ServiceAccountName = runner.ObjectMeta.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
if runnerSpec.AutomountServiceAccountToken != nil {
|
if runnerSpec.AutomountServiceAccountToken != nil {
|
||||||
pod.Spec.AutomountServiceAccountToken = runnerSpec.AutomountServiceAccountToken
|
pod.Spec.AutomountServiceAccountToken = runnerSpec.AutomountServiceAccountToken
|
||||||
}
|
}
|
||||||
|
|
@ -589,7 +704,7 @@ func runnerHookEnvs(pod *corev1.Pod) ([]corev1.EnvVar, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string) (corev1.Pod, error) {
|
func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string, useRunnerStatusUpdateHook bool) (corev1.Pod, error) {
|
||||||
var (
|
var (
|
||||||
privileged bool = true
|
privileged bool = true
|
||||||
dockerdInRunner bool = runnerSpec.DockerdWithinRunnerContainer != nil && *runnerSpec.DockerdWithinRunnerContainer
|
dockerdInRunner bool = runnerSpec.DockerdWithinRunnerContainer != nil && *runnerSpec.DockerdWithinRunnerContainer
|
||||||
|
|
@ -665,6 +780,10 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru
|
||||||
Name: EnvVarEphemeral,
|
Name: EnvVarEphemeral,
|
||||||
Value: fmt.Sprintf("%v", ephemeral),
|
Value: fmt.Sprintf("%v", ephemeral),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "RUNNER_STATUS_UPDATE_HOOK",
|
||||||
|
Value: fmt.Sprintf("%v", useRunnerStatusUpdateHook),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var seLinuxOptions *corev1.SELinuxOptions
|
var seLinuxOptions *corev1.SELinuxOptions
|
||||||
|
|
@ -962,8 +1081,8 @@ func newRunnerPodWithContainerMode(containerMode string, template corev1.Pod, ru
|
||||||
return *pod, nil
|
return *pod, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRunnerPod(template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string) (corev1.Pod, error) {
|
func newRunnerPod(template corev1.Pod, runnerSpec v1alpha1.RunnerConfig, defaultRunnerImage string, defaultRunnerImagePullSecrets []string, defaultDockerImage, defaultDockerRegistryMirror string, githubBaseURL string, useRunnerStatusUpdateHookEphemeralRole bool) (corev1.Pod, error) {
|
||||||
return newRunnerPodWithContainerMode("", template, runnerSpec, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL)
|
return newRunnerPodWithContainerMode("", template, runnerSpec, defaultRunnerImage, defaultRunnerImagePullSecrets, defaultDockerImage, defaultDockerRegistryMirror, githubBaseURL, useRunnerStatusUpdateHookEphemeralRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ type RunnerSetReconciler struct {
|
||||||
RunnerImagePullSecrets []string
|
RunnerImagePullSecrets []string
|
||||||
DockerImage string
|
DockerImage string
|
||||||
DockerRegistryMirror string
|
DockerRegistryMirror string
|
||||||
|
UseRunnerStatusUpdateHook bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnersets,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnersets,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
|
@ -221,7 +222,7 @@ func (r *RunnerSetReconciler) newStatefulSet(runnerSet *v1alpha1.RunnerSet) (*ap
|
||||||
|
|
||||||
template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunnerSetName, runnerSet.Name)
|
template.ObjectMeta.Labels = CloneAndAddLabel(template.ObjectMeta.Labels, LabelKeyRunnerSetName, runnerSet.Name)
|
||||||
|
|
||||||
pod, err := newRunnerPodWithContainerMode(runnerSet.Spec.RunnerConfig.ContainerMode, template, runnerSet.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, r.GitHubBaseURL)
|
pod, err := newRunnerPodWithContainerMode(runnerSet.Spec.RunnerConfig.ContainerMode, template, runnerSet.Spec.RunnerConfig, r.RunnerImage, r.RunnerImagePullSecrets, r.DockerImage, r.DockerRegistryMirror, r.GitHubBaseURL, r.UseRunnerStatusUpdateHook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
main.go
4
main.go
|
|
@ -70,6 +70,7 @@ func main() {
|
||||||
|
|
||||||
metricsAddr string
|
metricsAddr string
|
||||||
enableLeaderElection bool
|
enableLeaderElection bool
|
||||||
|
runnerStatusUpdateHook bool
|
||||||
leaderElectionId string
|
leaderElectionId string
|
||||||
port int
|
port int
|
||||||
syncPeriod time.Duration
|
syncPeriod time.Duration
|
||||||
|
|
@ -112,6 +113,7 @@ func main() {
|
||||||
flag.StringVar(&c.BasicauthUsername, "github-basicauth-username", c.BasicauthUsername, "Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API")
|
flag.StringVar(&c.BasicauthUsername, "github-basicauth-username", c.BasicauthUsername, "Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API")
|
||||||
flag.StringVar(&c.BasicauthPassword, "github-basicauth-password", c.BasicauthPassword, "Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API")
|
flag.StringVar(&c.BasicauthPassword, "github-basicauth-password", c.BasicauthPassword, "Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API")
|
||||||
flag.StringVar(&c.RunnerGitHubURL, "runner-github-url", c.RunnerGitHubURL, "GitHub URL to be used by runners during registration")
|
flag.StringVar(&c.RunnerGitHubURL, "runner-github-url", c.RunnerGitHubURL, "GitHub URL to be used by runners during registration")
|
||||||
|
flag.BoolVar(&runnerStatusUpdateHook, "runner-status-update-hook", false, "Use custom RBAC for runners (role, role binding and service account).")
|
||||||
flag.DurationVar(&gitHubAPICacheDuration, "github-api-cache-duration", 0, "DEPRECATED: The duration until the GitHub API cache expires. Setting this to e.g. 10m results in the controller tries its best not to make the same API call within 10m to reduce the chance of being rate-limited. Defaults to mostly the same value as sync-period. If you're tweaking this in order to make autoscaling more responsive, you'll probably want to tweak sync-period, too")
|
flag.DurationVar(&gitHubAPICacheDuration, "github-api-cache-duration", 0, "DEPRECATED: The duration until the GitHub API cache expires. Setting this to e.g. 10m results in the controller tries its best not to make the same API call within 10m to reduce the chance of being rate-limited. Defaults to mostly the same value as sync-period. If you're tweaking this in order to make autoscaling more responsive, you'll probably want to tweak sync-period, too")
|
||||||
flag.DurationVar(&defaultScaleDownDelay, "default-scale-down-delay", controllers.DefaultScaleDownDelay, "The approximate delay for a scale down followed by a scale up, used to prevent flapping (down->up->down->... loop)")
|
flag.DurationVar(&defaultScaleDownDelay, "default-scale-down-delay", controllers.DefaultScaleDownDelay, "The approximate delay for a scale down followed by a scale up, used to prevent flapping (down->up->down->... loop)")
|
||||||
flag.IntVar(&port, "port", 9443, "The port to which the admission webhook endpoint should bind")
|
flag.IntVar(&port, "port", 9443, "The port to which the admission webhook endpoint should bind")
|
||||||
|
|
@ -154,6 +156,7 @@ func main() {
|
||||||
GitHubClient: ghClient,
|
GitHubClient: ghClient,
|
||||||
DockerImage: dockerImage,
|
DockerImage: dockerImage,
|
||||||
DockerRegistryMirror: dockerRegistryMirror,
|
DockerRegistryMirror: dockerRegistryMirror,
|
||||||
|
UseRunnerStatusUpdateHook: runnerStatusUpdateHook,
|
||||||
// Defaults for self-hosted runner containers
|
// Defaults for self-hosted runner containers
|
||||||
RunnerImage: runnerImage,
|
RunnerImage: runnerImage,
|
||||||
RunnerImagePullSecrets: runnerImagePullSecrets,
|
RunnerImagePullSecrets: runnerImagePullSecrets,
|
||||||
|
|
@ -199,6 +202,7 @@ func main() {
|
||||||
// Defaults for self-hosted runner containers
|
// Defaults for self-hosted runner containers
|
||||||
RunnerImage: runnerImage,
|
RunnerImage: runnerImage,
|
||||||
RunnerImagePullSecrets: runnerImagePullSecrets,
|
RunnerImagePullSecrets: runnerImagePullSecrets,
|
||||||
|
UseRunnerStatusUpdateHook: runnerStatusUpdateHook,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = runnerSetReconciler.SetupWithManager(mgr); err != nil {
|
if err = runnerSetReconciler.SetupWithManager(mgr); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,10 @@ RUN mkdir /opt/hostedtoolcache \
|
||||||
|
|
||||||
# We place the scripts in `/usr/bin` so that users who extend this image can
|
# We place the scripts in `/usr/bin` so that users who extend this image can
|
||||||
# override them with scripts of the same name placed in `/usr/local/bin`.
|
# override them with scripts of the same name placed in `/usr/local/bin`.
|
||||||
COPY entrypoint.sh logger.bash /usr/bin/
|
COPY entrypoint.sh logger.bash update-status /usr/bin/
|
||||||
|
|
||||||
|
# Configure hooks folder structure.
|
||||||
|
COPY hooks /etc/arc/hooks/
|
||||||
|
|
||||||
ENV HOME=/home/runner
|
ENV HOME=/home/runner
|
||||||
# Add the Python "User Script Directory" to the PATH
|
# Add the Python "User Script Directory" to the PATH
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@ source logger.bash
|
||||||
RUNNER_ASSETS_DIR=${RUNNER_ASSETS_DIR:-/runnertmp}
|
RUNNER_ASSETS_DIR=${RUNNER_ASSETS_DIR:-/runnertmp}
|
||||||
RUNNER_HOME=${RUNNER_HOME:-/runner}
|
RUNNER_HOME=${RUNNER_HOME:-/runner}
|
||||||
|
|
||||||
|
# Let GitHub runner execute these hooks. These environment variables are used by GitHub's Runner as described here
|
||||||
|
# https://github.com/actions/runner/blob/main/docs/adrs/1751-runner-job-hooks.md
|
||||||
|
# Scripts referenced in the ACTIONS_RUNNER_HOOK_ environment variables must end in .sh or .ps1
|
||||||
|
# for it to become a valid hook script, otherwise GitHub will fail to run the hook
|
||||||
|
export ACTIONS_RUNNER_HOOK_JOB_STARTED=/etc/arc/hooks/job-started.sh
|
||||||
|
export ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/etc/arc/hooks/job-completed.sh
|
||||||
|
|
||||||
if [ ! -z "${STARTUP_DELAY_IN_SECONDS}" ]; then
|
if [ ! -z "${STARTUP_DELAY_IN_SECONDS}" ]; then
|
||||||
log.notice "Delaying startup by ${STARTUP_DELAY_IN_SECONDS} seconds"
|
log.notice "Delaying startup by ${STARTUP_DELAY_IN_SECONDS} seconds"
|
||||||
sleep ${STARTUP_DELAY_IN_SECONDS}
|
sleep ${STARTUP_DELAY_IN_SECONDS}
|
||||||
|
|
@ -77,6 +84,8 @@ if [ "${DISABLE_RUNNER_UPDATE:-}" == "true" ]; then
|
||||||
log.debug 'Passing --disableupdate to config.sh to disable automatic runner updates.'
|
log.debug 'Passing --disableupdate to config.sh to disable automatic runner updates.'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
update-status "Registering"
|
||||||
|
|
||||||
retries_left=10
|
retries_left=10
|
||||||
while [[ ${retries_left} -gt 0 ]]; do
|
while [[ ${retries_left} -gt 0 ]]; do
|
||||||
log.debug 'Configuring the runner.'
|
log.debug 'Configuring the runner.'
|
||||||
|
|
@ -155,4 +164,5 @@ unset RUNNER_NAME RUNNER_REPO RUNNER_TOKEN STARTUP_DELAY_IN_SECONDS DISABLE_WAIT
|
||||||
if [ -z "${UNITTEST:-}" ]; then
|
if [ -z "${UNITTEST:-}" ]; then
|
||||||
mapfile -t env </etc/environment
|
mapfile -t env </etc/environment
|
||||||
fi
|
fi
|
||||||
|
update-status "Idle"
|
||||||
exec env -- "${env[@]}" ./run.sh
|
exec env -- "${env[@]}" ./run.sh
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
|
||||||
|
exec update-status Idle
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=runner/logger.bash
|
||||||
|
source logger.bash
|
||||||
|
|
||||||
|
log.debug "Running ARC Job Completed Hooks"
|
||||||
|
|
||||||
|
for hook in /etc/arc/hooks/job-completed.d/*; do
|
||||||
|
log.debug "Running hook: $hook"
|
||||||
|
"$hook" "$@"
|
||||||
|
done
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
|
||||||
|
exec update-status Running "Run $GITHUB_RUN_ID from $GITHUB_REPOSITORY"
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=runner/logger.bash
|
||||||
|
source logger.bash
|
||||||
|
|
||||||
|
log.debug "Running ARC Job Started Hooks"
|
||||||
|
|
||||||
|
for hook in /etc/arc/hooks/job-started.d/*; do
|
||||||
|
log.debug "Running hook: $hook"
|
||||||
|
"$hook" "$@"
|
||||||
|
done
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
if [[ ${1:-} == '' ]]; then
|
||||||
|
# shellcheck source=runner/logger.bash
|
||||||
|
source logger.bash
|
||||||
|
log.error "Missing required argument -- '<phase>'"
|
||||||
|
exit 64
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${RUNNER_STATUS_UPDATE_HOOK:-false} == true ]]; then
|
||||||
|
|
||||||
|
apiserver=https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}
|
||||||
|
serviceaccount=/var/run/secrets/kubernetes.io/serviceaccount
|
||||||
|
namespace=$(cat ${serviceaccount}/namespace)
|
||||||
|
token=$(cat ${serviceaccount}/token)
|
||||||
|
phase=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
jq -n --arg phase "$phase" --arg message "${*:-}" '.status.phase = $phase | .status.message = $message' | curl \
|
||||||
|
--cacert ${serviceaccount}/ca.crt \
|
||||||
|
--data @- \
|
||||||
|
--noproxy '*' \
|
||||||
|
--header "Content-Type: application/merge-patch+json" \
|
||||||
|
--header "Authorization: Bearer ${token}" \
|
||||||
|
--show-error \
|
||||||
|
--silent \
|
||||||
|
--request PATCH \
|
||||||
|
"${apiserver}/apis/actions.summerwind.dev/v1alpha1/namespaces/${namespace}/runners/${HOSTNAME}/status"
|
||||||
|
1>&-
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue