Files
actions-runner-controller/controllers/runnerreplicaset_controller.go
T

206 lines
6.4 KiB
Go
Raw Normal View History

2020-02-21 03:01:52 +09:00
/*
Copyright 2020 The actions-runner-controller authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
2021-05-21 09:10:47 +09:00
"reflect"
"time"
"github.com/go-logr/logr"
2021-02-25 00:38:55 +01:00
kerrors "k8s.io/apimachinery/pkg/api/errors"
2020-02-21 03:01:52 +09:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-06-22 17:55:06 +09:00
"github.com/actions-runner-controller/actions-runner-controller/api/v1alpha1"
2020-02-21 03:01:52 +09:00
)
2020-03-10 09:14:11 +09:00
// RunnerReplicaSetReconciler reconciles a Runner object
type RunnerReplicaSetReconciler struct {
2020-02-21 03:01:52 +09:00
client.Client
2022-07-12 09:45:00 +09:00
Log logr.Logger
Recorder record.EventRecorder
Scheme *runtime.Scheme
Name string
2020-02-21 03:01:52 +09:00
}
const (
SyncTimeAnnotationKey = "sync-time"
)
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerreplicasets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerreplicasets/finalizers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runnerreplicasets/status,verbs=get;update;patch
2020-02-21 03:01:52 +09:00
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=actions.summerwind.dev,resources=runners/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch
2020-02-21 03:01:52 +09:00
func (r *RunnerReplicaSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("runnerreplicaset", req.NamespacedName)
2020-02-21 03:01:52 +09:00
2020-03-10 09:14:11 +09:00
var rs v1alpha1.RunnerReplicaSet
2020-02-21 03:01:52 +09:00
if err := r.Get(ctx, req.NamespacedName, &rs); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if !rs.ObjectMeta.DeletionTimestamp.IsZero() {
// RunnerReplicaSet cannot be gracefuly removed.
// That means any runner that is running a job can be prematurely terminated.
// To gracefully remove a RunnerReplicaSet, scale it down to zero first, observe RunnerReplicaSet's status replicas,
// and remove it only after the status replicas becomes zero.
2020-02-21 03:01:52 +09:00
return ctrl.Result{}, nil
}
if rs.ObjectMeta.Labels == nil {
rs.ObjectMeta.Labels = map[string]string{}
}
// Template hash is usually set by the upstream controller(RunnerDeplloyment controller) on authoring
// RunerReplicaset resource, but it may be missing when the user directly created RunnerReplicaSet.
// As a template hash is required by by the runner replica management, we dynamically add it here without ever persisting it.
if rs.ObjectMeta.Labels[LabelKeyRunnerTemplateHash] == "" {
template := rs.Spec.DeepCopy()
template.Replicas = nil
template.EffectiveTime = nil
templateHash := ComputeHash(template)
log.Info("Using auto-generated template hash", "value", templateHash)
rs.ObjectMeta.Labels = CloneAndAddLabel(rs.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash)
rs.Spec.Template.ObjectMeta.Labels = CloneAndAddLabel(rs.Spec.Template.ObjectMeta.Labels, LabelKeyRunnerTemplateHash, templateHash)
}
selector, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
if err != nil {
return ctrl.Result{}, err
}
// Get the Runners managed by the target RunnerReplicaSet
var runnerList v1alpha1.RunnerList
if err := r.List(
ctx,
&runnerList,
client.InNamespace(req.Namespace),
client.MatchingLabelsSelector{Selector: selector},
); err != nil {
if !kerrors.IsNotFound(err) {
return ctrl.Result{}, err
}
}
replicas := 1
if rs.Spec.Replicas != nil {
replicas = *rs.Spec.Replicas
}
effectiveTime := rs.Spec.EffectiveTime
ephemeral := rs.Spec.Template.Spec.Ephemeral == nil || *rs.Spec.Template.Spec.Ephemeral
desired, err := r.newRunner(rs)
if err != nil {
log.Error(err, "Could not create runner")
2020-02-21 03:01:52 +09:00
return ctrl.Result{}, err
}
2020-02-21 03:01:52 +09:00
var live []client.Object
for _, r := range runnerList.Items {
r := r
live = append(live, &r)
}
2021-03-20 07:34:25 +09:00
res, err := syncRunnerPodsOwners(ctx, r.Client, log, effectiveTime, replicas, func() client.Object { return desired.DeepCopy() }, ephemeral, live)
if err != nil || res == nil {
return ctrl.Result{}, err
}
2020-02-21 03:01:52 +09:00
var (
status v1alpha1.RunnerReplicaSetStatus
2020-02-21 03:01:52 +09:00
current, available, ready int
)
2020-02-21 03:01:52 +09:00
for _, o := range res.currentObjects {
current += o.total
available += o.running
ready += o.running
2020-02-21 03:01:52 +09:00
}
2021-05-21 09:10:47 +09:00
status.Replicas = &current
status.AvailableReplicas = &available
status.ReadyReplicas = &ready
if !reflect.DeepEqual(rs.Status, status) {
2020-02-21 03:01:52 +09:00
updated := rs.DeepCopy()
2021-05-21 09:10:47 +09:00
updated.Status = status
2020-02-21 03:01:52 +09:00
2021-05-21 09:10:47 +09:00
if err := r.Status().Patch(ctx, updated, client.MergeFrom(&rs)); err != nil {
log.Info("Failed to update runnerreplicaset status. Retrying immediately", "error", err.Error())
return ctrl.Result{
Requeue: true,
}, nil
2020-02-21 03:01:52 +09:00
}
}
return ctrl.Result{}, nil
}
2020-03-10 09:14:11 +09:00
func (r *RunnerReplicaSetReconciler) newRunner(rs v1alpha1.RunnerReplicaSet) (v1alpha1.Runner, error) {
// Note that the upstream controller (runnerdeployment) is expected to add
// the "runner template hash" label to the template.meta which is necessary to make this controller work correctly
2020-02-26 21:23:23 +09:00
objectMeta := rs.Spec.Template.ObjectMeta.DeepCopy()
2020-03-15 21:50:45 +09:00
objectMeta.GenerateName = rs.ObjectMeta.Name + "-"
2020-02-26 21:23:23 +09:00
objectMeta.Namespace = rs.ObjectMeta.Namespace
if objectMeta.Annotations == nil {
objectMeta.Annotations = map[string]string{}
}
objectMeta.Annotations[SyncTimeAnnotationKey] = time.Now().Format(time.RFC3339)
2020-02-26 21:23:23 +09:00
2020-02-21 03:01:52 +09:00
runner := v1alpha1.Runner{
2020-02-26 21:23:23 +09:00
TypeMeta: metav1.TypeMeta{},
ObjectMeta: *objectMeta,
Spec: rs.Spec.Template.Spec,
2020-02-21 03:01:52 +09:00
}
if err := ctrl.SetControllerReference(&rs, &runner, r.Scheme); err != nil {
return runner, err
}
return runner, nil
}
2020-03-10 09:14:11 +09:00
func (r *RunnerReplicaSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
name := "runnerreplicaset-controller"
if r.Name != "" {
name = r.Name
}
r.Recorder = mgr.GetEventRecorderFor(name)
2020-02-21 03:01:52 +09:00
return ctrl.NewControllerManagedBy(mgr).
2020-03-10 09:14:11 +09:00
For(&v1alpha1.RunnerReplicaSet{}).
2020-02-21 03:01:52 +09:00
Owns(&v1alpha1.Runner{}).
Named(name).
2020-02-21 03:01:52 +09:00
Complete(r)
}