1736 lines
59 KiB
Go
1736 lines
59 KiB
Go
package actionsgithubcom
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
"github.com/go-logr/logr"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
|
"github.com/actions/actions-runner-controller/build"
|
|
"github.com/actions/actions-runner-controller/github/actions"
|
|
"github.com/actions/actions-runner-controller/github/actions/fake"
|
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
|
)
|
|
|
|
const (
|
|
autoscalingRunnerSetTestTimeout = time.Second * 5
|
|
autoscalingRunnerSetTestInterval = time.Millisecond * 250
|
|
autoscalingRunnerSetTestGitHubToken = "gh_token"
|
|
)
|
|
|
|
var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|
var ctx context.Context
|
|
var mgr ctrl.Manager
|
|
var controller *AutoscalingRunnerSetReconciler
|
|
var autoscalingNS *corev1.Namespace
|
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
|
var configSecret *corev1.Secret
|
|
|
|
var originalBuildVersion string
|
|
buildVersion := "0.1.0"
|
|
|
|
BeforeAll(func() {
|
|
originalBuildVersion = build.Version
|
|
build.Version = buildVersion
|
|
})
|
|
|
|
AfterAll(func() {
|
|
build.Version = originalBuildVersion
|
|
})
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
controller = &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
})
|
|
|
|
Context("When creating a new AutoScalingRunnerSet", func() {
|
|
It("It should create/add all required resources for a new AutoScalingRunnerSet (finalizer, runnerscaleset, ephemeralrunnerset, listener)", func() {
|
|
// Check if finalizer is added
|
|
created := new(v1alpha1.AutoscalingRunnerSet)
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, created)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(created.Finalizers) == 0 {
|
|
return "", nil
|
|
}
|
|
return created.Finalizers[0], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer")
|
|
|
|
// Check if runner scale set is created on service
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, created)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, ok := created.Annotations[runnerScaleSetIdAnnotationKey]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
if _, ok := created.Annotations[AnnotationKeyGitHubRunnerGroupName]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
return fmt.Sprintf("%s_%s", created.Annotations[runnerScaleSetIdAnnotationKey], created.Annotations[AnnotationKeyGitHubRunnerGroupName]), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo("1_testgroup"), "RunnerScaleSet should be created/fetched and update the AutoScalingRunnerSet's annotation")
|
|
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, created)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, ok := created.Labels[LabelKeyGitHubOrganization]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
if _, ok := created.Labels[LabelKeyGitHubRepository]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
return fmt.Sprintf("%s/%s", created.Labels[LabelKeyGitHubOrganization], created.Labels[LabelKeyGitHubRepository]), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo("owner/repo"), "RunnerScaleSet should be created/fetched and update the AutoScalingRunnerSet's label")
|
|
|
|
// Check if ephemeral runner set is created
|
|
Eventually(
|
|
func() (int, error) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(runnerSetList.Items), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(1), "Only one EphemeralRunnerSet should be created")
|
|
|
|
// Check if listener is created
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, new(v1alpha1.AutoscalingListener))
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "Listener should be created")
|
|
|
|
// Check if status is updated
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
|
Expect(len(runnerSetList.Items)).To(BeEquivalentTo(1), "Only one EphemeralRunnerSet should be created")
|
|
})
|
|
})
|
|
|
|
Context("When deleting a new AutoScalingRunnerSet", func() {
|
|
It("It should cleanup all resources for a deleting AutoScalingRunnerSet before removing it", func() {
|
|
// Wait till the listener is created
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, new(v1alpha1.AutoscalingListener))
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "Listener should be created")
|
|
|
|
// Delete the AutoScalingRunnerSet
|
|
err := k8sClient.Delete(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete AutoScalingRunnerSet")
|
|
|
|
// Check if the listener is deleted
|
|
Eventually(
|
|
func() error {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, new(v1alpha1.AutoscalingListener))
|
|
if err != nil && errors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("listener is not deleted")
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "Listener should be deleted")
|
|
|
|
// Check if all the EphemeralRunnerSet is deleted
|
|
Eventually(
|
|
func() error {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(runnerSetList.Items) != 0 {
|
|
return fmt.Errorf("EphemeralRunnerSet is not deleted, count=%v", len(runnerSetList.Items))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "All EphemeralRunnerSet should be deleted")
|
|
|
|
// Check if the AutoScalingRunnerSet is deleted
|
|
Eventually(
|
|
func() error {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, new(v1alpha1.AutoscalingRunnerSet))
|
|
if err != nil && errors.IsNotFound(err) {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("AutoScalingRunnerSet is not deleted")
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "AutoScalingRunnerSet should be deleted")
|
|
})
|
|
})
|
|
|
|
Context("When updating a new AutoScalingRunnerSet", func() {
|
|
It("It should re-create EphemeralRunnerSet and Listener as needed when updating AutoScalingRunnerSet", func() {
|
|
// Wait till the listener is created
|
|
listener := new(v1alpha1.AutoscalingListener)
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "Listener should be created")
|
|
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
|
Expect(len(runnerSetList.Items)).To(Equal(1), "There should be 1 EphemeralRunnerSet")
|
|
runnerSet := runnerSetList.Items[0]
|
|
|
|
// Update the AutoScalingRunnerSet.Spec.Template
|
|
// This should trigger re-creation of EphemeralRunnerSet and Listener
|
|
patched := autoscalingRunnerSet.DeepCopy()
|
|
patched.Spec.Template.Spec.PriorityClassName = "test-priority-class"
|
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
|
autoscalingRunnerSet = patched.DeepCopy()
|
|
|
|
// We should create a new EphemeralRunnerSet and delete the old one, eventually, we will have only one EphemeralRunnerSet
|
|
Eventually(
|
|
func() (string, error) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(runnerSetList.Items) != 1 {
|
|
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
|
}
|
|
|
|
return runnerSetList.Items[0].Labels[labelKeyRunnerSpecHash], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(runnerSet.Labels[labelKeyRunnerSpecHash]), "New EphemeralRunnerSet should be created")
|
|
|
|
// We should create a new listener
|
|
Eventually(
|
|
func() (string, error) {
|
|
listener := new(v1alpha1.AutoscalingListener)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return listener.Spec.EphemeralRunnerSetName, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(runnerSet.Name), "New Listener should be created")
|
|
|
|
// Only update the Spec for the AutoScalingListener
|
|
// This should trigger re-creation of the Listener only
|
|
runnerSetList = new(v1alpha1.EphemeralRunnerSetList)
|
|
err = k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
|
Expect(len(runnerSetList.Items)).To(Equal(1), "There should be 1 EphemeralRunnerSet")
|
|
runnerSet = runnerSetList.Items[0]
|
|
|
|
listener = new(v1alpha1.AutoscalingListener)
|
|
err = k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to get Listener")
|
|
|
|
patched = autoscalingRunnerSet.DeepCopy()
|
|
min := 10
|
|
patched.Spec.MinRunners = &min
|
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
|
|
|
// We should not re-create a new EphemeralRunnerSet
|
|
Consistently(
|
|
func() (string, error) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(runnerSetList.Items) != 1 {
|
|
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
|
}
|
|
|
|
return string(runnerSetList.Items[0].UID), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(string(runnerSet.UID)), "New EphemeralRunnerSet should not be created")
|
|
|
|
// We should only re-create a new listener
|
|
Eventually(
|
|
func() (string, error) {
|
|
listener := new(v1alpha1.AutoscalingListener)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(listener.UID), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(string(listener.UID)), "New Listener should be created")
|
|
})
|
|
|
|
It("It should update RunnerScaleSet's runner group on service when it changes", func() {
|
|
updated := new(v1alpha1.AutoscalingRunnerSet)
|
|
// Wait till the listener is created
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, new(v1alpha1.AutoscalingListener))
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(Succeed(), "Listener should be created")
|
|
|
|
patched := autoscalingRunnerSet.DeepCopy()
|
|
patched.Spec.RunnerGroup = "testgroup2"
|
|
err := k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
|
|
|
// Check if AutoScalingRunnerSet has the new runner group in its annotation
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, ok := updated.Annotations[AnnotationKeyGitHubRunnerGroupName]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
return updated.Annotations[AnnotationKeyGitHubRunnerGroupName], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo("testgroup2"), "AutoScalingRunnerSet should have the new runner group in its annotation")
|
|
|
|
// delete the annotation and it should be re-added
|
|
patched = autoscalingRunnerSet.DeepCopy()
|
|
delete(patched.Annotations, AnnotationKeyGitHubRunnerGroupName)
|
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
|
|
|
// Check if AutoScalingRunnerSet still has the runner group in its annotation
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if _, ok := updated.Annotations[AnnotationKeyGitHubRunnerGroupName]; !ok {
|
|
return "", nil
|
|
}
|
|
|
|
return updated.Annotations[AnnotationKeyGitHubRunnerGroupName], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo("testgroup2"), "AutoScalingRunnerSet should have the runner group in its annotation")
|
|
})
|
|
})
|
|
|
|
Context("When updating an AutoscalingRunnerSet with running or pending jobs", func() {
|
|
It("It should wait for running and pending jobs to finish before applying the update. Update Strategy is set to eventual.", func() {
|
|
// Switch update strategy to eventual (drain jobs )
|
|
controller.UpdateStrategy = UpdateStrategyEventual
|
|
// Wait till the listener is created
|
|
listener := new(v1alpha1.AutoscalingListener)
|
|
Eventually(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(Succeed(), "Listener should be created")
|
|
|
|
// Wait till the ephemeral runner set is created
|
|
Eventually(
|
|
func() (int, error) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return len(runnerSetList.Items), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(1), "Only one EphemeralRunnerSet should be created")
|
|
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
|
|
|
// Emulate running and pending jobs
|
|
runnerSet := runnerSetList.Items[0]
|
|
activeRunnerSet := runnerSet.DeepCopy()
|
|
activeRunnerSet.Status.CurrentReplicas = 6
|
|
activeRunnerSet.Status.FailedEphemeralRunners = 1
|
|
activeRunnerSet.Status.RunningEphemeralRunners = 2
|
|
activeRunnerSet.Status.PendingEphemeralRunners = 3
|
|
|
|
desiredStatus := v1alpha1.AutoscalingRunnerSetStatus{
|
|
CurrentRunners: activeRunnerSet.Status.CurrentReplicas,
|
|
State: "",
|
|
PendingEphemeralRunners: activeRunnerSet.Status.PendingEphemeralRunners,
|
|
RunningEphemeralRunners: activeRunnerSet.Status.RunningEphemeralRunners,
|
|
FailedEphemeralRunners: activeRunnerSet.Status.FailedEphemeralRunners,
|
|
}
|
|
|
|
err = k8sClient.Status().Patch(ctx, activeRunnerSet, client.MergeFrom(&runnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to patch runner set status")
|
|
|
|
Eventually(
|
|
func() (v1alpha1.AutoscalingRunnerSetStatus, error) {
|
|
updated := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
|
if err != nil {
|
|
return v1alpha1.AutoscalingRunnerSetStatus{}, fmt.Errorf("failed to get AutoScalingRunnerSet: %w", err)
|
|
}
|
|
return updated.Status, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(desiredStatus), "AutoScalingRunnerSet status should be updated")
|
|
|
|
// Patch the AutoScalingRunnerSet image which should trigger
|
|
// the recreation of the Listener and EphemeralRunnerSet
|
|
patched := autoscalingRunnerSet.DeepCopy()
|
|
patched.Spec.Template.Spec = corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/abcd:1.1.1",
|
|
},
|
|
},
|
|
}
|
|
// patched.Spec.Template.Spec.PriorityClassName = "test-priority-class"
|
|
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
|
autoscalingRunnerSet = patched.DeepCopy()
|
|
|
|
// The EphemeralRunnerSet should not be recreated
|
|
Consistently(
|
|
func() (string, error) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to fetch AutoScalingRunnerSet")
|
|
return runnerSetList.Items[0].Name, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(Equal(activeRunnerSet.Name), "The EphemeralRunnerSet should not be recreated")
|
|
|
|
// The listener should not be recreated
|
|
Consistently(
|
|
func() error {
|
|
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).ShouldNot(Succeed(), "Listener should not be recreated")
|
|
})
|
|
})
|
|
|
|
It("Should update Status on EphemeralRunnerSet status Update", func() {
|
|
ars := new(v1alpha1.AutoscalingRunnerSet)
|
|
Eventually(
|
|
func() (bool, error) {
|
|
err := k8sClient.Get(
|
|
ctx,
|
|
client.ObjectKey{
|
|
Name: autoscalingRunnerSet.Name,
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
},
|
|
ars,
|
|
)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "AutoscalingRunnerSet should be created")
|
|
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
Eventually(func() (int, error) {
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(ars.Namespace))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(runnerSetList.Items), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(1), "Failed to fetch runner set list")
|
|
|
|
runnerSet := runnerSetList.Items[0]
|
|
statusUpdate := runnerSet.DeepCopy()
|
|
statusUpdate.Status.CurrentReplicas = 6
|
|
statusUpdate.Status.FailedEphemeralRunners = 1
|
|
statusUpdate.Status.RunningEphemeralRunners = 2
|
|
statusUpdate.Status.PendingEphemeralRunners = 3
|
|
|
|
desiredStatus := v1alpha1.AutoscalingRunnerSetStatus{
|
|
CurrentRunners: statusUpdate.Status.CurrentReplicas,
|
|
State: "",
|
|
PendingEphemeralRunners: statusUpdate.Status.PendingEphemeralRunners,
|
|
RunningEphemeralRunners: statusUpdate.Status.RunningEphemeralRunners,
|
|
FailedEphemeralRunners: statusUpdate.Status.FailedEphemeralRunners,
|
|
}
|
|
|
|
err := k8sClient.Status().Patch(ctx, statusUpdate, client.MergeFrom(&runnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "Failed to patch runner set status")
|
|
|
|
Eventually(
|
|
func() (v1alpha1.AutoscalingRunnerSetStatus, error) {
|
|
updated := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
|
if err != nil {
|
|
return v1alpha1.AutoscalingRunnerSetStatus{}, fmt.Errorf("failed to get AutoScalingRunnerSet: %w", err)
|
|
}
|
|
return updated.Status, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(desiredStatus), "AutoScalingRunnerSet status should be updated")
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Test AutoScalingController updates", Ordered, func() {
|
|
var originalBuildVersion string
|
|
buildVersion := "0.1.0"
|
|
|
|
BeforeAll(func() {
|
|
originalBuildVersion = build.Version
|
|
build.Version = buildVersion
|
|
})
|
|
|
|
AfterAll(func() {
|
|
build.Version = originalBuildVersion
|
|
})
|
|
|
|
Context("Creating autoscaling runner set with RunnerScaleSetName set", func() {
|
|
var ctx context.Context
|
|
var mgr ctrl.Manager
|
|
var autoscalingNS *corev1.Namespace
|
|
var autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet
|
|
var configSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
originalBuildVersion = build.Version
|
|
ctx = context.Background()
|
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
controller := &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(
|
|
fake.WithDefaultClient(
|
|
fake.NewFakeClient(
|
|
fake.WithUpdateRunnerScaleSet(
|
|
&actions.RunnerScaleSet{
|
|
Id: 1,
|
|
Name: "testset_update",
|
|
RunnerGroupId: 1,
|
|
RunnerGroupName: "testgroup",
|
|
Labels: []actions.Label{{Type: "test", Name: "test"}},
|
|
RunnerSetting: actions.RunnerSetting{},
|
|
CreatedOn: time.Now(),
|
|
RunnerJitConfigUrl: "test.test.test",
|
|
Statistics: nil,
|
|
},
|
|
nil,
|
|
),
|
|
),
|
|
nil,
|
|
),
|
|
),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
})
|
|
|
|
It("It should be create AutoScalingRunnerSet and has annotation for the RunnerScaleSetName", func() {
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet = &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerScaleSetName: "testset",
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
// Wait for the AutoScalingRunnerSet to be created with right annotation
|
|
ars := new(v1alpha1.AutoscalingRunnerSet)
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, ars)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if val, ok := ars.Annotations[runnerScaleSetNameAnnotationKey]; ok {
|
|
return val, nil
|
|
}
|
|
|
|
return "", nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(autoscalingRunnerSet.Spec.RunnerScaleSetName), "AutoScalingRunnerSet should have annotation for the RunnerScaleSetName")
|
|
|
|
update := autoscalingRunnerSet.DeepCopy()
|
|
update.Spec.RunnerScaleSetName = "testset_update"
|
|
|
|
err = k8sClient.Patch(ctx, update, client.MergeFrom(autoscalingRunnerSet))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to update AutoScalingRunnerSet")
|
|
|
|
// Wait for the AutoScalingRunnerSet to be updated with right annotation
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, ars)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if val, ok := ars.Annotations[runnerScaleSetNameAnnotationKey]; ok {
|
|
return val, nil
|
|
}
|
|
|
|
return "", nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(update.Spec.RunnerScaleSetName), "AutoScalingRunnerSet should have a updated annotation for the RunnerScaleSetName")
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Test AutoscalingController creation failures", Ordered, func() {
|
|
var originalBuildVersion string
|
|
buildVersion := "0.1.0"
|
|
|
|
BeforeAll(func() {
|
|
originalBuildVersion = build.Version
|
|
build.Version = buildVersion
|
|
})
|
|
|
|
AfterAll(func() {
|
|
build.Version = originalBuildVersion
|
|
})
|
|
Context("When autoscaling runner set creation fails on the client", func() {
|
|
var ctx context.Context
|
|
var mgr ctrl.Manager
|
|
var autoscalingNS *corev1.Namespace
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
|
|
|
controller := &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
})
|
|
|
|
It("It should be able to clean up if annotation related to scale set id does not exist", func() {
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
// wait for the finalizer to be added
|
|
ars := new(v1alpha1.AutoscalingRunnerSet)
|
|
Eventually(
|
|
func() (string, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, ars)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(ars.Finalizers) == 0 {
|
|
return "", nil
|
|
}
|
|
return ars.Finalizers[0], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer")
|
|
|
|
ars.ObjectMeta.Annotations = make(map[string]string)
|
|
err = k8sClient.Update(ctx, ars)
|
|
Expect(err).NotTo(HaveOccurred(), "Update autoscaling runner set without annotation should be successful")
|
|
|
|
Eventually(
|
|
func() (bool, error) {
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, ars)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(ars.ObjectMeta.Annotations) == 0, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(true), "Autoscaling runner set should be updated with empty annotations")
|
|
|
|
err = k8sClient.Delete(ctx, ars)
|
|
Expect(err).NotTo(HaveOccurred(), "Delete autoscaling runner set should be successful")
|
|
|
|
Eventually(
|
|
func() (bool, error) {
|
|
updated := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
|
if err == nil {
|
|
return false, nil
|
|
}
|
|
if !errors.IsNotFound(err) {
|
|
return false, err
|
|
}
|
|
|
|
return !controllerutil.ContainsFinalizer(updated, autoscalingRunnerSetFinalizerName), nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(true), "Finalizer and resource should eventually be deleted")
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Test client optional configuration", Ordered, func() {
|
|
var originalBuildVersion string
|
|
buildVersion := "0.1.0"
|
|
|
|
BeforeAll(func() {
|
|
originalBuildVersion = build.Version
|
|
build.Version = buildVersion
|
|
})
|
|
|
|
AfterAll(func() {
|
|
build.Version = originalBuildVersion
|
|
})
|
|
Context("When specifying a proxy", func() {
|
|
var ctx context.Context
|
|
var mgr ctrl.Manager
|
|
var autoscalingNS *corev1.Namespace
|
|
var configSecret *corev1.Secret
|
|
var controller *AutoscalingRunnerSetReconciler
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
controller = &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: actions.NewMultiClient("test", logr.Discard()),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
})
|
|
|
|
It("should be able to make requests to a server using a proxy", func() {
|
|
serverSuccessfullyCalled := false
|
|
proxy := testserver.New(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
serverSuccessfullyCalled = true
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "http://example.com/org/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Proxy: &v1alpha1.ProxyConfig{
|
|
HTTP: &v1alpha1.ProxyServerConfig{
|
|
Url: proxy.URL,
|
|
},
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
// wait for server to be called
|
|
Eventually(
|
|
func() (bool, error) {
|
|
return serverSuccessfullyCalled, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
1*time.Nanosecond,
|
|
).Should(BeTrue(), "server was not called")
|
|
})
|
|
|
|
It("should be able to make requests to a server using a proxy with user info", func() {
|
|
serverSuccessfullyCalled := false
|
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
header := r.Header.Get("Proxy-Authorization")
|
|
Expect(header).NotTo(BeEmpty())
|
|
|
|
header = strings.TrimPrefix(header, "Basic ")
|
|
decoded, err := base64.StdEncoding.DecodeString(header)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(string(decoded)).To(Equal("test:password"))
|
|
|
|
serverSuccessfullyCalled = true
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
GinkgoT().Cleanup(func() {
|
|
proxy.Close()
|
|
})
|
|
|
|
secretCredentials := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "proxy-credentials",
|
|
Namespace: autoscalingNS.Name,
|
|
},
|
|
Data: map[string][]byte{
|
|
"username": []byte("test"),
|
|
"password": []byte("password"),
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, secretCredentials)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create secret credentials")
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "http://example.com/org/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Proxy: &v1alpha1.ProxyConfig{
|
|
HTTP: &v1alpha1.ProxyServerConfig{
|
|
Url: proxy.URL,
|
|
CredentialSecretRef: "proxy-credentials",
|
|
},
|
|
},
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
// wait for server to be called
|
|
Eventually(
|
|
func() (bool, error) {
|
|
return serverSuccessfullyCalled, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
1*time.Nanosecond,
|
|
).Should(BeTrue(), "server was not called")
|
|
})
|
|
})
|
|
|
|
Context("When specifying a configmap for root CAs", func() {
|
|
var ctx context.Context
|
|
var mgr ctrl.Manager
|
|
var autoscalingNS *corev1.Namespace
|
|
var configSecret *corev1.Secret
|
|
var rootCAConfigMap *corev1.ConfigMap
|
|
var controller *AutoscalingRunnerSetReconciler
|
|
|
|
BeforeEach(func() {
|
|
ctx = context.Background()
|
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
cert, err := os.ReadFile(filepath.Join(
|
|
"../../",
|
|
"github",
|
|
"actions",
|
|
"testdata",
|
|
"rootCA.crt",
|
|
))
|
|
Expect(err).NotTo(HaveOccurred(), "failed to read root CA cert")
|
|
rootCAConfigMap = &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "root-ca-configmap",
|
|
Namespace: autoscalingNS.Name,
|
|
},
|
|
Data: map[string]string{
|
|
"rootCA.crt": string(cert),
|
|
},
|
|
}
|
|
err = k8sClient.Create(ctx, rootCAConfigMap)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
|
|
|
controller = &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err = controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
})
|
|
|
|
It("should be able to make requests to a server using root CAs", func() {
|
|
controller.ActionsClient = actions.NewMultiClient("test", logr.Discard())
|
|
|
|
certsFolder := filepath.Join(
|
|
"../../",
|
|
"github",
|
|
"actions",
|
|
"testdata",
|
|
)
|
|
certPath := filepath.Join(certsFolder, "server.crt")
|
|
keyPath := filepath.Join(certsFolder, "server.key")
|
|
|
|
serverSuccessfullyCalled := false
|
|
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
serverSuccessfullyCalled = true
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to load server cert")
|
|
|
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
server.StartTLS()
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
|
GitHubConfigSecret: configSecret.Name,
|
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: rootCAConfigMap.Name,
|
|
},
|
|
Key: "rootCA.crt",
|
|
},
|
|
},
|
|
},
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
// wait for server to be called
|
|
Eventually(
|
|
func() (bool, error) {
|
|
return serverSuccessfullyCalled, nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
1*time.Nanosecond,
|
|
).Should(BeTrue(), "server was not called")
|
|
})
|
|
|
|
It("it creates a listener referencing the right configmap for TLS", func() {
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: rootCAConfigMap.Name,
|
|
},
|
|
Key: "rootCA.crt",
|
|
},
|
|
},
|
|
},
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
Eventually(
|
|
func(g Gomega) {
|
|
listener := new(v1alpha1.AutoscalingListener)
|
|
err := k8sClient.Get(
|
|
ctx,
|
|
client.ObjectKey{
|
|
Name: scaleSetListenerName(autoscalingRunnerSet),
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
},
|
|
listener,
|
|
)
|
|
g.Expect(err).NotTo(HaveOccurred(), "failed to get listener")
|
|
|
|
g.Expect(listener.Spec.GitHubServerTLS).NotTo(BeNil(), "listener does not have TLS config")
|
|
g.Expect(listener.Spec.GitHubServerTLS).To(BeEquivalentTo(autoscalingRunnerSet.Spec.GitHubServerTLS), "listener does not have TLS config")
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(Succeed(), "tls config is incorrect")
|
|
})
|
|
|
|
It("it creates an ephemeral runner set referencing the right configmap for TLS", func() {
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
|
LocalObjectReference: corev1.LocalObjectReference{
|
|
Name: rootCAConfigMap.Name,
|
|
},
|
|
Key: "rootCA.crt",
|
|
},
|
|
},
|
|
},
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
Eventually(
|
|
func(g Gomega) {
|
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
|
g.Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
|
g.Expect(runnerSetList.Items).To(HaveLen(1), "expected 1 EphemeralRunnerSet to be created")
|
|
|
|
runnerSet := &runnerSetList.Items[0]
|
|
|
|
g.Expect(runnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS).NotTo(BeNil(), "expected EphemeralRunnerSpec.GitHubServerTLS to be set")
|
|
g.Expect(runnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS).To(BeEquivalentTo(autoscalingRunnerSet.Spec.GitHubServerTLS), "EphemeralRunnerSpec does not have TLS config")
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(Succeed())
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Test external permissions cleanup", Ordered, func() {
|
|
var originalBuildVersion string
|
|
buildVersion := "0.1.0"
|
|
|
|
BeforeAll(func() {
|
|
originalBuildVersion = build.Version
|
|
build.Version = buildVersion
|
|
})
|
|
|
|
AfterAll(func() {
|
|
build.Version = originalBuildVersion
|
|
})
|
|
|
|
It("Should clean up kubernetes mode permissions", func() {
|
|
ctx := context.Background()
|
|
autoscalingNS, mgr := createNamespace(GinkgoT(), k8sClient)
|
|
|
|
configSecret := createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
controller := &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/name": "gha-runner-scale-set",
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
Annotations: map[string]string{
|
|
AnnotationKeyKubernetesModeRoleBindingName: "kube-mode-role-binding",
|
|
AnnotationKeyKubernetesModeRoleName: "kube-mode-role",
|
|
AnnotationKeyKubernetesModeServiceAccountName: "kube-mode-service-account",
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
role := &rbacv1.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyKubernetesModeRoleName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, role)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create kubernetes mode role")
|
|
|
|
serviceAccount := &corev1.ServiceAccount{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyKubernetesModeServiceAccountName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, serviceAccount)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create kubernetes mode service account")
|
|
|
|
roleBinding := &rbacv1.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyKubernetesModeRoleBindingName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
Subjects: []rbacv1.Subject{
|
|
{
|
|
Kind: "ServiceAccount",
|
|
Name: serviceAccount.Name,
|
|
Namespace: serviceAccount.Namespace,
|
|
},
|
|
},
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: rbacv1.GroupName,
|
|
// Kind is the type of resource being referenced
|
|
Kind: "Role",
|
|
Name: role.Name,
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, roleBinding)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create kubernetes mode role binding")
|
|
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
Eventually(
|
|
func() (string, error) {
|
|
created := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, created)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(created.Finalizers) == 0 {
|
|
return "", nil
|
|
}
|
|
return created.Finalizers[0], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer")
|
|
|
|
err = k8sClient.Delete(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete autoscaling runner set")
|
|
|
|
err = k8sClient.Delete(ctx, roleBinding)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete kubernetes mode role binding")
|
|
|
|
err = k8sClient.Delete(ctx, role)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete kubernetes mode role")
|
|
|
|
err = k8sClient.Delete(ctx, serviceAccount)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete kubernetes mode service account")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(rbacv1.RoleBinding)
|
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
|
Name: roleBinding.Name,
|
|
Namespace: roleBinding.Namespace,
|
|
}, r)
|
|
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected role binding to be cleaned up")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(rbacv1.Role)
|
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
|
Name: role.Name,
|
|
Namespace: role.Namespace,
|
|
}, r)
|
|
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected role to be cleaned up")
|
|
})
|
|
|
|
It("Should clean up manager permissions and no-permission service account", func() {
|
|
ctx := context.Background()
|
|
autoscalingNS, mgr := createNamespace(GinkgoT(), k8sClient)
|
|
|
|
controller := &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/name": "gha-runner-scale-set",
|
|
LabelKeyKubernetesVersion: buildVersion,
|
|
},
|
|
Annotations: map[string]string{
|
|
AnnotationKeyManagerRoleName: "manager-role",
|
|
AnnotationKeyManagerRoleBindingName: "manager-role-binding",
|
|
AnnotationKeyGitHubSecretName: "gh-secret-name",
|
|
AnnotationKeyNoPermissionServiceAccountName: "no-permission-sa",
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyGitHubSecretName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
Data: map[string][]byte{
|
|
"github_token": []byte(defaultGitHubToken),
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(context.Background(), secret)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create github secret")
|
|
|
|
autoscalingRunnerSet.Spec.GitHubConfigSecret = secret.Name
|
|
|
|
role := &rbacv1.Role{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyManagerRoleName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, role)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create manager role")
|
|
|
|
roleBinding := &rbacv1.RoleBinding{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyManagerRoleBindingName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
RoleRef: rbacv1.RoleRef{
|
|
APIGroup: rbacv1.GroupName,
|
|
Kind: "Role",
|
|
Name: role.Name,
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, roleBinding)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create manager role binding")
|
|
|
|
noPermissionServiceAccount := &corev1.ServiceAccount{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: autoscalingRunnerSet.Annotations[AnnotationKeyNoPermissionServiceAccountName],
|
|
Namespace: autoscalingRunnerSet.Namespace,
|
|
Finalizers: []string{AutoscalingRunnerSetCleanupFinalizerName},
|
|
},
|
|
}
|
|
|
|
err = k8sClient.Create(ctx, noPermissionServiceAccount)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create no permission service account")
|
|
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
|
|
|
Eventually(
|
|
func() (string, error) {
|
|
created := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, created)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(created.Finalizers) == 0 {
|
|
return "", nil
|
|
}
|
|
return created.Finalizers[0], nil
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeEquivalentTo(autoscalingRunnerSetFinalizerName), "AutoScalingRunnerSet should have a finalizer")
|
|
|
|
err = k8sClient.Delete(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete autoscaling runner set")
|
|
|
|
err = k8sClient.Delete(ctx, noPermissionServiceAccount)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete no permission service account")
|
|
|
|
err = k8sClient.Delete(ctx, secret)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete GitHub secret")
|
|
|
|
err = k8sClient.Delete(ctx, roleBinding)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete manager role binding")
|
|
|
|
err = k8sClient.Delete(ctx, role)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to delete manager role")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(corev1.ServiceAccount)
|
|
err := k8sClient.Get(
|
|
ctx,
|
|
types.NamespacedName{
|
|
Name: noPermissionServiceAccount.Name,
|
|
Namespace: noPermissionServiceAccount.Namespace,
|
|
},
|
|
r,
|
|
)
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected no permission service account to be cleaned up")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(corev1.Secret)
|
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
|
Name: secret.Name,
|
|
Namespace: secret.Namespace,
|
|
}, r)
|
|
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected role binding to be cleaned up")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(rbacv1.RoleBinding)
|
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
|
Name: roleBinding.Name,
|
|
Namespace: roleBinding.Namespace,
|
|
}, r)
|
|
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected role binding to be cleaned up")
|
|
|
|
Eventually(
|
|
func() bool {
|
|
r := new(rbacv1.Role)
|
|
err := k8sClient.Get(
|
|
ctx,
|
|
types.NamespacedName{
|
|
Name: role.Name,
|
|
Namespace: role.Namespace,
|
|
},
|
|
r,
|
|
)
|
|
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue(), "Expected role to be cleaned up")
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Test resource version and build version mismatch", func() {
|
|
It("Should delete and recreate the autoscaling runner set to match the build version", func() {
|
|
ctx := context.Background()
|
|
autoscalingNS, mgr := createNamespace(GinkgoT(), k8sClient)
|
|
|
|
configSecret := createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
|
|
|
controller := &AutoscalingRunnerSetReconciler{
|
|
Client: mgr.GetClient(),
|
|
Scheme: mgr.GetScheme(),
|
|
Log: logf.Log,
|
|
ControllerNamespace: autoscalingNS.Name,
|
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
|
ActionsClient: fake.NewMultiClient(),
|
|
}
|
|
err := controller.SetupWithManager(mgr)
|
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
|
|
|
originalVersion := build.Version
|
|
defer func() {
|
|
build.Version = originalVersion
|
|
}()
|
|
build.Version = "0.2.0"
|
|
|
|
min := 1
|
|
max := 10
|
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-asrs",
|
|
Namespace: autoscalingNS.Name,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/name": "gha-runner-scale-set",
|
|
"app.kubernetes.io/version": "0.1.0",
|
|
},
|
|
Annotations: map[string]string{
|
|
AnnotationKeyKubernetesModeRoleBindingName: "kube-mode-role-binding",
|
|
AnnotationKeyKubernetesModeRoleName: "kube-mode-role",
|
|
AnnotationKeyKubernetesModeServiceAccountName: "kube-mode-service-account",
|
|
},
|
|
},
|
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
|
GitHubConfigSecret: configSecret.Name,
|
|
MaxRunners: &max,
|
|
MinRunners: &min,
|
|
RunnerGroup: "testgroup",
|
|
Template: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "runner",
|
|
Image: "ghcr.io/actions/runner",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// create autoscaling runner set before starting a manager
|
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
startManagers(GinkgoT(), mgr)
|
|
|
|
Eventually(
|
|
func() bool {
|
|
ars := new(v1alpha1.AutoscalingRunnerSet)
|
|
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Name}, ars)
|
|
return errors.IsNotFound(err)
|
|
},
|
|
autoscalingRunnerSetTestTimeout,
|
|
autoscalingRunnerSetTestInterval,
|
|
).Should(BeTrue())
|
|
})
|
|
})
|