package main import ( "context" "encoding/json" "fmt" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" jsonpatch "github.com/evanphx/json-patch" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) type AutoScalerKubernetesManager struct { *kubernetes.Clientset logger logr.Logger } func NewKubernetesManager(logger *logr.Logger) (*AutoScalerKubernetesManager, error) { conf, err := rest.InClusterConfig() if err != nil { return nil, err } kubeClient, err := kubernetes.NewForConfig(conf) if err != nil { return nil, err } var manager = &AutoScalerKubernetesManager{ Clientset: kubeClient, logger: logger.WithName("KubernetesManager"), } return manager, nil } func (k *AutoScalerKubernetesManager) ScaleEphemeralRunnerSet(ctx context.Context, namespace, resourceName string, runnerCount int) error { original := &v1alpha1.EphemeralRunnerSet{ Spec: v1alpha1.EphemeralRunnerSetSpec{ Replicas: -1, }, } originalJson, err := json.Marshal(original) if err != nil { k.logger.Error(err, "could not marshal empty ephemeral runner set") } patch := &v1alpha1.EphemeralRunnerSet{ Spec: v1alpha1.EphemeralRunnerSetSpec{ Replicas: runnerCount, }, } patchJson, err := json.Marshal(patch) if err != nil { k.logger.Error(err, "could not marshal patch ephemeral runner set") } mergePatch, err := jsonpatch.CreateMergePatch(originalJson, patchJson) if err != nil { k.logger.Error(err, "could not create merge patch json for ephemeral runner set") } k.logger.Info("Created merge patch json for EphemeralRunnerSet update", "json", string(mergePatch)) patchedEphemeralRunnerSet := &v1alpha1.EphemeralRunnerSet{} err = k.RESTClient(). Patch(types.MergePatchType). Prefix("apis", "actions.github.com", "v1alpha1"). Namespace(namespace). Resource("EphemeralRunnerSets"). Name(resourceName). Body([]byte(mergePatch)). Do(ctx). Into(patchedEphemeralRunnerSet) if err != nil { return fmt.Errorf("could not patch ephemeral runner set , patch JSON: %s, error: %w", string(mergePatch), err) } k.logger.Info("Ephemeral runner set scaled.", "namespace", namespace, "name", resourceName, "replicas", patchedEphemeralRunnerSet.Spec.Replicas) return nil } func (k *AutoScalerKubernetesManager) UpdateEphemeralRunnerWithJobInfo(ctx context.Context, namespace, resourceName, ownerName, repositoryName, jobWorkflowRef, jobDisplayName string, workflowRunId, jobRequestId int64) error { original := &v1alpha1.EphemeralRunner{} originalJson, err := json.Marshal(original) if err != nil { return fmt.Errorf("could not marshal empty ephemeral runner, error: %w", err) } patch := &v1alpha1.EphemeralRunner{ Status: v1alpha1.EphemeralRunnerStatus{ JobRequestId: jobRequestId, JobRepositoryName: fmt.Sprintf("%s/%s", ownerName, repositoryName), WorkflowRunId: workflowRunId, JobWorkflowRef: jobWorkflowRef, JobDisplayName: jobDisplayName, }, } patchedJson, err := json.Marshal(patch) if err != nil { return fmt.Errorf("could not marshal patched ephemeral runner, error: %w", err) } mergePatch, err := jsonpatch.CreateMergePatch(originalJson, patchedJson) if err != nil { k.logger.Error(err, "could not create merge patch json for ephemeral runner") } k.logger.Info("Created merge patch json for EphemeralRunner status update", "json", string(mergePatch)) patchedStatus := &v1alpha1.EphemeralRunner{} err = k.RESTClient(). Patch(types.MergePatchType). Prefix("apis", "actions.github.com", "v1alpha1"). Namespace(namespace). Resource("EphemeralRunners"). Name(resourceName). SubResource("status"). Body(mergePatch). Do(ctx). Into(patchedStatus) if err != nil { return fmt.Errorf("could not patch ephemeral runner status, patch JSON: %s, error: %w", string(mergePatch), err) } return nil }