129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
"github.com/summerwind/actions-runner-controller/github"
|
|
"gomodules.xyz/jsonpatch/v2"
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/client-go/tools/record"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
|
)
|
|
|
|
// +kubebuilder:webhook:path=/mutate-runner-set-pod,mutating=true,failurePolicy=ignore,groups="",resources=pods,verbs=create,versions=v1,name=mutate-runner-pod.webhook.actions.summerwind.dev,sideEffects=None,admissionReviewVersions=v1beta1
|
|
|
|
type PodRunnerTokenInjector struct {
|
|
client.Client
|
|
|
|
Name string
|
|
Log logr.Logger
|
|
Recorder record.EventRecorder
|
|
GitHubClient *github.Client
|
|
decoder *admission.Decoder
|
|
}
|
|
|
|
func (t *PodRunnerTokenInjector) Handle(ctx context.Context, req admission.Request) admission.Response {
|
|
var pod corev1.Pod
|
|
err := t.decoder.Decode(req, &pod)
|
|
if err != nil {
|
|
t.Log.Error(err, "Failed to decode request object")
|
|
return admission.Errored(http.StatusBadRequest, err)
|
|
}
|
|
|
|
if pod.Annotations == nil {
|
|
pod.Annotations = map[string]string{}
|
|
}
|
|
|
|
var runnerContainer *corev1.Container
|
|
|
|
for i := range pod.Spec.Containers {
|
|
c := pod.Spec.Containers[i]
|
|
|
|
if c.Name == "runner" {
|
|
runnerContainer = &c
|
|
}
|
|
}
|
|
|
|
if runnerContainer == nil {
|
|
return newEmptyResponse()
|
|
}
|
|
|
|
enterprise, okEnterprise := getEnv(runnerContainer, "RUNNER_ENTERPRISE")
|
|
repo, okRepo := getEnv(runnerContainer, "RUNNER_REPO")
|
|
org, okOrg := getEnv(runnerContainer, "RUNNER_ORG")
|
|
if !okRepo || !okOrg || !okEnterprise {
|
|
return newEmptyResponse()
|
|
}
|
|
|
|
rt, err := t.GitHubClient.GetRegistrationToken(context.Background(), enterprise, org, repo, pod.Name)
|
|
if err != nil {
|
|
t.Log.Error(err, "Failed to get new registration token")
|
|
return admission.Errored(http.StatusInternalServerError, err)
|
|
}
|
|
|
|
ts := rt.GetExpiresAt().Format(time.RFC3339)
|
|
|
|
updated := mutatePod(&pod, *rt.Token)
|
|
|
|
updated.Annotations["actions-runner-controller/token-expires-at"] = ts
|
|
|
|
if pod.Spec.RestartPolicy != corev1.RestartPolicyOnFailure {
|
|
updated.Spec.RestartPolicy = corev1.RestartPolicyOnFailure
|
|
}
|
|
|
|
buf, err := json.Marshal(updated)
|
|
if err != nil {
|
|
t.Log.Error(err, "Failed to encode new object")
|
|
return admission.Errored(http.StatusInternalServerError, err)
|
|
}
|
|
|
|
res := admission.PatchResponseFromRaw(req.Object.Raw, buf)
|
|
return res
|
|
}
|
|
|
|
func getEnv(container *corev1.Container, key string) (string, bool) {
|
|
for _, env := range container.Env {
|
|
if env.Name == key {
|
|
return env.Value, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
func (t *PodRunnerTokenInjector) InjectDecoder(d *admission.Decoder) error {
|
|
t.decoder = d
|
|
return nil
|
|
}
|
|
|
|
func newEmptyResponse() admission.Response {
|
|
pt := admissionv1.PatchTypeJSONPatch
|
|
return admission.Response{
|
|
Patches: []jsonpatch.Operation{},
|
|
AdmissionResponse: admissionv1.AdmissionResponse{
|
|
Allowed: true,
|
|
PatchType: &pt,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *PodRunnerTokenInjector) SetupWithManager(mgr ctrl.Manager) error {
|
|
name := "pod-runner-token-injector"
|
|
if r.Name != "" {
|
|
name = r.Name
|
|
}
|
|
|
|
r.Recorder = mgr.GetEventRecorderFor(name)
|
|
|
|
mgr.GetWebhookServer().Register("/mutate-runner-set-pod", &admission.Webhook{Handler: r})
|
|
|
|
return nil
|
|
}
|