Adding parameter to configure the runner set name. (#2279)

Co-authored-by: TingluoHuang <TingluoHuang@github.com>
This commit is contained in:
Chris Patterson 2023-03-03 08:36:14 -05:00 committed by GitHub
parent 00996ec799
commit 41f2ca3ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 252 additions and 7 deletions

View File

@ -57,6 +57,9 @@ type AutoscalingRunnerSetSpec struct {
// +optional // +optional
RunnerGroup string `json:"runnerGroup,omitempty"` RunnerGroup string `json:"runnerGroup,omitempty"`
// +optional
RunnerScaleSetName string `json:"runnerScaleSetName,omitempty"`
// +optional // +optional
Proxy *ProxyConfig `json:"proxy,omitempty"` Proxy *ProxyConfig `json:"proxy,omitempty"`
@ -205,6 +208,7 @@ func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
GitHubConfigUrl string GitHubConfigUrl string
GitHubConfigSecret string GitHubConfigSecret string
RunnerGroup string RunnerGroup string
RunnerScaleSetName string
Proxy *ProxyConfig Proxy *ProxyConfig
GitHubServerTLS *GitHubServerTLSConfig GitHubServerTLS *GitHubServerTLSConfig
Template corev1.PodTemplateSpec Template corev1.PodTemplateSpec
@ -213,6 +217,7 @@ func (ars *AutoscalingRunnerSet) RunnerSetSpecHash() string {
GitHubConfigUrl: ars.Spec.GitHubConfigUrl, GitHubConfigUrl: ars.Spec.GitHubConfigUrl,
GitHubConfigSecret: ars.Spec.GitHubConfigSecret, GitHubConfigSecret: ars.Spec.GitHubConfigSecret,
RunnerGroup: ars.Spec.RunnerGroup, RunnerGroup: ars.Spec.RunnerGroup,
RunnerScaleSetName: ars.Spec.RunnerScaleSetName,
Proxy: ars.Spec.Proxy, Proxy: ars.Spec.Proxy,
GitHubServerTLS: ars.Spec.GitHubServerTLS, GitHubServerTLS: ars.Spec.GitHubServerTLS,
Template: ars.Spec.Template, Template: ars.Spec.Template,

View File

@ -86,6 +86,8 @@ spec:
type: object type: object
runnerGroup: runnerGroup:
type: string type: string
runnerScaleSetName:
type: string
template: template:
description: Required description: Required
properties: properties:

View File

@ -17,6 +17,9 @@ spec:
{{- with .Values.runnerGroup }} {{- with .Values.runnerGroup }}
runnerGroup: {{ . }} runnerGroup: {{ . }}
{{- end }} {{- end }}
{{- with .Values.runnerScaleSetName }}
runnerScaleSetName: {{ . }}
{{- end }}
{{- if .Values.proxy }} {{- if .Values.proxy }}
proxy: proxy:

View File

@ -310,6 +310,53 @@ func TestTemplateRenderedAutoScalingRunnerSet(t *testing.T) {
assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image) assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image)
} }
func TestTemplateRenderedAutoScalingRunnerSet_RunnerScaleSetName(t *testing.T) {
t.Parallel()
// Path to the helm chart we will test
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
releaseName := "test-runners"
namespaceName := "test-" + strings.ToLower(random.UniqueId())
options := &helm.Options{
SetValues: map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"runnerScaleSetName": "test-runner-scale-set-name",
},
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
}
output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/autoscalingrunnerset.yaml"})
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
assert.Equal(t, namespaceName, ars.Namespace)
assert.Equal(t, "test-runners", ars.Name)
assert.Equal(t, "gha-runner-scale-set", ars.Labels["app.kubernetes.io/name"])
assert.Equal(t, "test-runners", ars.Labels["app.kubernetes.io/instance"])
assert.Equal(t, "https://github.com/actions", ars.Spec.GitHubConfigUrl)
assert.Equal(t, "test-runners-gha-runner-scale-set-github-secret", ars.Spec.GitHubConfigSecret)
assert.Equal(t, "test-runner-scale-set-name", ars.Spec.RunnerScaleSetName)
assert.Empty(t, ars.Spec.RunnerGroup, "RunnerGroup should be empty")
assert.Nil(t, ars.Spec.MinRunners, "MinRunners should be nil")
assert.Nil(t, ars.Spec.MaxRunners, "MaxRunners should be nil")
assert.Nil(t, ars.Spec.Proxy, "Proxy should be nil")
assert.Nil(t, ars.Spec.GitHubServerTLS, "GitHubServerTLS should be nil")
assert.NotNil(t, ars.Spec.Template.Spec, "Template.Spec should not be nil")
assert.Len(t, ars.Spec.Template.Spec.Containers, 1, "Template.Spec should have 1 container")
assert.Equal(t, "runner", ars.Spec.Template.Spec.Containers[0].Name)
assert.Equal(t, "ghcr.io/actions/actions-runner:latest", ars.Spec.Template.Spec.Containers[0].Image)
}
func TestTemplateRenderedAutoScalingRunnerSet_ProvideMetadata(t *testing.T) { func TestTemplateRenderedAutoScalingRunnerSet_ProvideMetadata(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -44,6 +44,9 @@ githubConfigSecret:
# runnerGroup: "default" # runnerGroup: "default"
## name of the runner scale set to create. Defaults to the helm release name
# runnerScaleSetName: ""
## template is the PodSpec for each runner Pod ## template is the PodSpec for each runner Pod
template: template:
spec: spec:

View File

@ -86,6 +86,8 @@ spec:
type: object type: object
runnerGroup: runnerGroup:
type: string type: string
runnerScaleSetName:
type: string
template: template:
description: Required description: Required
properties: properties:

View File

@ -99,6 +99,7 @@ func (r *AutoscalingListenerReconciler) Reconcile(ctx context.Context, req ctrl.
} }
log.Info("Successfully removed finalizer after cleanup") log.Info("Successfully removed finalizer after cleanup")
return ctrl.Result{}, nil
} }
if !controllerutil.ContainsFinalizer(autoscalingListener, autoscalingListenerFinalizerName) { if !controllerutil.ContainsFinalizer(autoscalingListener, autoscalingListenerFinalizerName) {

View File

@ -46,6 +46,7 @@ const (
LabelKeyRunnerSpecHash = "runner-spec-hash" LabelKeyRunnerSpecHash = "runner-spec-hash"
autoscalingRunnerSetFinalizerName = "autoscalingrunnerset.actions.github.com/finalizer" autoscalingRunnerSetFinalizerName = "autoscalingrunnerset.actions.github.com/finalizer"
runnerScaleSetIdKey = "runner-scale-set-id" runnerScaleSetIdKey = "runner-scale-set-id"
runnerScaleSetNameKey = "runner-scale-set-name"
runnerScaleSetRunnerGroupNameKey = "runner-scale-set-runner-group-name" runnerScaleSetRunnerGroupNameKey = "runner-scale-set-runner-group-name"
) )
@ -123,6 +124,7 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
} }
log.Info("Successfully removed finalizer after cleanup") log.Info("Successfully removed finalizer after cleanup")
return ctrl.Result{}, nil
} }
if !controllerutil.ContainsFinalizer(autoscalingRunnerSet, autoscalingRunnerSetFinalizerName) { if !controllerutil.ContainsFinalizer(autoscalingRunnerSet, autoscalingRunnerSetFinalizerName) {
@ -158,6 +160,13 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
return r.updateRunnerScaleSetRunnerGroup(ctx, autoscalingRunnerSet, log) return r.updateRunnerScaleSetRunnerGroup(ctx, autoscalingRunnerSet, log)
} }
// Make sure the runner scale set name is up to date
currentRunnerScaleSetName, ok := autoscalingRunnerSet.Annotations[runnerScaleSetNameKey]
if !ok || (len(autoscalingRunnerSet.Spec.RunnerScaleSetName) > 0 && !strings.EqualFold(currentRunnerScaleSetName, autoscalingRunnerSet.Spec.RunnerScaleSetName)) {
log.Info("AutoScalingRunnerSet runner scale set name changed. Updating the runner scale set.")
return r.updateRunnerScaleSetName(ctx, autoscalingRunnerSet, log)
}
secret := new(corev1.Secret) secret := new(corev1.Secret)
if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, secret); err != nil { if err := r.Get(ctx, types.NamespacedName{Namespace: autoscalingRunnerSet.Namespace, Name: autoscalingRunnerSet.Spec.GitHubConfigSecret}, secret); err != nil {
log.Error(err, "Failed to find GitHub config secret.", log.Error(err, "Failed to find GitHub config secret.",
@ -297,11 +306,14 @@ func (r *AutoscalingRunnerSetReconciler) deleteEphemeralRunnerSets(ctx context.C
func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) { func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) {
logger.Info("Creating a new runner scale set") logger.Info("Creating a new runner scale set")
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet) actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 {
autoscalingRunnerSet.Spec.RunnerScaleSetName = autoscalingRunnerSet.Name
}
if err != nil { if err != nil {
logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set") logger.Error(err, "Failed to initialize Actions service client for creating a new runner scale set")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
runnerScaleSet, err := actionsClient.GetRunnerScaleSet(ctx, autoscalingRunnerSet.Name) runnerScaleSet, err := actionsClient.GetRunnerScaleSet(ctx, autoscalingRunnerSet.Spec.RunnerScaleSetName)
if err != nil { if err != nil {
logger.Error(err, "Failed to get runner scale set from Actions service") logger.Error(err, "Failed to get runner scale set from Actions service")
return ctrl.Result{}, err return ctrl.Result{}, err
@ -322,11 +334,11 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
runnerScaleSet, err = actionsClient.CreateRunnerScaleSet( runnerScaleSet, err = actionsClient.CreateRunnerScaleSet(
ctx, ctx,
&actions.RunnerScaleSet{ &actions.RunnerScaleSet{
Name: autoscalingRunnerSet.Name, Name: autoscalingRunnerSet.Spec.RunnerScaleSetName,
RunnerGroupId: runnerGroupId, RunnerGroupId: runnerGroupId,
Labels: []actions.Label{ Labels: []actions.Label{
{ {
Name: autoscalingRunnerSet.Name, Name: autoscalingRunnerSet.Spec.RunnerScaleSetName,
Type: "System", Type: "System",
}, },
}, },
@ -346,16 +358,20 @@ func (r *AutoscalingRunnerSetReconciler) createRunnerScaleSet(ctx context.Contex
autoscalingRunnerSet.Annotations = map[string]string{} autoscalingRunnerSet.Annotations = map[string]string{}
} }
logger.Info("Adding runner scale set ID and runner group name as an annotation") logger.Info("Adding runner scale set ID, name and runner group name as an annotation")
if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) { if err = patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
obj.Annotations[runnerScaleSetNameKey] = runnerScaleSet.Name
obj.Annotations[runnerScaleSetIdKey] = strconv.Itoa(runnerScaleSet.Id) obj.Annotations[runnerScaleSetIdKey] = strconv.Itoa(runnerScaleSet.Id)
obj.Annotations[runnerScaleSetRunnerGroupNameKey] = runnerScaleSet.RunnerGroupName obj.Annotations[runnerScaleSetRunnerGroupNameKey] = runnerScaleSet.RunnerGroupName
}); err != nil { }); err != nil {
logger.Error(err, "Failed to add runner scale set ID and runner group name as an annotation") logger.Error(err, "Failed to add runner scale set ID, name and runner group name as an annotation")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
logger.Info("Updated with runner scale set ID and runner group name as an annotation") logger.Info("Updated with runner scale set ID, name and runner group name as an annotation",
"id", runnerScaleSet.Id,
"name", runnerScaleSet.Name,
"runnerGroupName", runnerScaleSet.RunnerGroupName)
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
@ -383,7 +399,7 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
runnerGroupId = int(runnerGroup.ID) runnerGroupId = int(runnerGroup.ID)
} }
updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetId, &actions.RunnerScaleSet{Name: autoscalingRunnerSet.Name, RunnerGroupId: runnerGroupId}) updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetId, &actions.RunnerScaleSet{RunnerGroupId: runnerGroupId})
if err != nil { if err != nil {
logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetId) logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetId)
return ctrl.Result{}, err return ctrl.Result{}, err
@ -401,6 +417,42 @@ func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetRunnerGroup(ctx con
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *AutoscalingRunnerSetReconciler) updateRunnerScaleSetName(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) (ctrl.Result, error) {
runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdKey])
if err != nil {
logger.Error(err, "Failed to parse runner scale set ID")
return ctrl.Result{}, err
}
if len(autoscalingRunnerSet.Spec.RunnerScaleSetName) == 0 {
logger.Info("Runner scale set name is not specified, skipping")
return ctrl.Result{}, nil
}
actionsClient, err := r.actionsClientFor(ctx, autoscalingRunnerSet)
if err != nil {
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
return ctrl.Result{}, err
}
updatedRunnerScaleSet, err := actionsClient.UpdateRunnerScaleSet(ctx, runnerScaleSetId, &actions.RunnerScaleSet{Name: autoscalingRunnerSet.Spec.RunnerScaleSetName})
if err != nil {
logger.Error(err, "Failed to update runner scale set", "runnerScaleSetId", runnerScaleSetId)
return ctrl.Result{}, err
}
logger.Info("Updating runner scale set name as an annotation")
if err := patch(ctx, r.Client, autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
obj.Annotations[runnerScaleSetNameKey] = updatedRunnerScaleSet.Name
}); err != nil {
logger.Error(err, "Failed to update runner scale set name annotation")
return ctrl.Result{}, err
}
logger.Info("Updated runner scale set with match name", "name", updatedRunnerScaleSet.Name)
return ctrl.Result{}, nil
}
func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) error { func (r *AutoscalingRunnerSetReconciler) deleteRunnerScaleSet(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, logger logr.Logger) error {
logger.Info("Deleting the runner scale set from Actions service") logger.Info("Deleting the runner scale set from Actions service")
runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdKey]) runnerScaleSetId, err := strconv.Atoi(autoscalingRunnerSet.Annotations[runnerScaleSetIdKey])

View File

@ -400,6 +400,129 @@ var _ = Describe("Test AutoScalingRunnerSet controller", func() {
}) })
}) })
var _ = Describe("Test AutoScalingController updates", func() {
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() {
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,
},
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[runnerScaleSetNameKey]; 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[runnerScaleSetNameKey]; 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", func() { var _ = Describe("Test AutoscalingController creation failures", func() {
Context("When autoscaling runner set creation fails on the client", func() { Context("When autoscaling runner set creation fails on the client", func() {
var ctx context.Context var ctx context.Context

View File

@ -38,6 +38,13 @@ func WithCreateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Optio
} }
} }
func WithUpdateRunnerScaleSet(scaleSet *actions.RunnerScaleSet, err error) Option {
return func(f *FakeClient) {
f.updateRunnerScaleSetResult.RunnerScaleSet = scaleSet
f.updateRunnerScaleSetResult.err = err
}
}
var defaultRunnerScaleSet = &actions.RunnerScaleSet{ var defaultRunnerScaleSet = &actions.RunnerScaleSet{
Id: 1, Id: 1,
Name: "testset", Name: "testset",