mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-09-30 21:41:04 +02:00
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
304 lines
8.2 KiB
Go
304 lines
8.2 KiB
Go
package actionsgithubcom
|
|
|
|
import (
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
|
"github.com/actions/actions-runner-controller/hash"
|
|
corev1 "k8s.io/api/core/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
const (
|
|
resourceCacheInitialEntries = 4096
|
|
resourceCacheInitialMainUIDEntries = 4096
|
|
resourceCacheInitialOwnerEntries = 8
|
|
resourceCacheMaxDependencyRefs = 4
|
|
)
|
|
|
|
type ResourceCacheObjectRef struct {
|
|
ObjectType schema.GroupVersionKind
|
|
Namespace string
|
|
Name string
|
|
UID types.UID
|
|
ResourceVersion string
|
|
}
|
|
|
|
type ResourceCacheKey struct {
|
|
MainUID types.UID
|
|
Namespace string
|
|
Name string
|
|
}
|
|
|
|
type ResourceCacheValue[T client.Object] struct {
|
|
MainObject ResourceCacheObjectRef
|
|
ResourceVersion string
|
|
dependencyKey resourceCacheDependencyKey
|
|
Object T
|
|
}
|
|
|
|
type resourceCacheDependencyKey struct {
|
|
count int
|
|
refs [resourceCacheMaxDependencyRefs]ResourceCacheObjectRef
|
|
}
|
|
|
|
type ResourceCache struct {
|
|
autoscalingListener *resourceCacheState[*v1alpha1.AutoscalingListener]
|
|
ephemeralRunnerSet *resourceCacheState[*v1alpha1.EphemeralRunnerSet]
|
|
listenerPod *resourceCacheState[*corev1.Pod]
|
|
listenerServiceAccount *resourceCacheState[*corev1.ServiceAccount]
|
|
listenerRole *resourceCacheState[*rbacv1.Role]
|
|
listenerRoleBinding *resourceCacheState[*rbacv1.RoleBinding]
|
|
}
|
|
|
|
func NewResourceCache() ResourceCache {
|
|
return ResourceCache{
|
|
autoscalingListener: newResourceCacheState[*v1alpha1.AutoscalingListener](),
|
|
ephemeralRunnerSet: newResourceCacheState[*v1alpha1.EphemeralRunnerSet](),
|
|
listenerPod: newResourceCacheState[*corev1.Pod](),
|
|
listenerServiceAccount: newResourceCacheState[*corev1.ServiceAccount](),
|
|
listenerRole: newResourceCacheState[*rbacv1.Role](),
|
|
listenerRoleBinding: newResourceCacheState[*rbacv1.RoleBinding](),
|
|
}
|
|
}
|
|
|
|
type resourceCacheState[T client.Object] struct {
|
|
mu sync.RWMutex
|
|
entries map[ResourceCacheKey]ResourceCacheValue[T]
|
|
entriesByMainUID map[types.UID]map[ResourceCacheKey]struct{}
|
|
}
|
|
|
|
func newResourceCacheState[T client.Object]() *resourceCacheState[T] {
|
|
return &resourceCacheState[T]{
|
|
entries: make(map[ResourceCacheKey]ResourceCacheValue[T], resourceCacheInitialEntries),
|
|
entriesByMainUID: make(map[types.UID]map[ResourceCacheKey]struct{}, resourceCacheInitialMainUIDEntries),
|
|
}
|
|
}
|
|
|
|
func (s *resourceCacheState[T]) Get(
|
|
mainObject client.Object,
|
|
desiredObject T,
|
|
dependencies ...client.Object,
|
|
) (T, bool) {
|
|
var zero T
|
|
if s == nil || isNilResourceCacheObject(mainObject) || isNilResourceCacheObject(desiredObject) {
|
|
return zero, false
|
|
}
|
|
dependencyKey, ok := newResourceCacheDependencyKey(dependencies...)
|
|
if !ok {
|
|
return zero, false
|
|
}
|
|
if mainObject.GetUID() == "" {
|
|
return zero, false
|
|
}
|
|
|
|
key := newResourceCacheKey(mainObject, desiredObject)
|
|
mainObjectRef := newResourceCacheObjectRef(mainObject)
|
|
|
|
s.mu.RLock()
|
|
value, ok := s.entries[key]
|
|
if ok && value.MainObject == mainObjectRef && value.dependencyKey.Equal(dependencyKey) {
|
|
s.mu.RUnlock()
|
|
return value.Object, true
|
|
}
|
|
s.mu.RUnlock()
|
|
|
|
return zero, false
|
|
}
|
|
|
|
func (s *resourceCacheState[T]) Upsert(
|
|
mainObject client.Object,
|
|
desiredObject T,
|
|
dependencies ...client.Object,
|
|
) (ResourceCacheValue[T], bool) {
|
|
var zero ResourceCacheValue[T]
|
|
if s == nil || isNilResourceCacheObject(mainObject) || isNilResourceCacheObject(desiredObject) {
|
|
return zero, false
|
|
}
|
|
dependencyKey, ok := newResourceCacheDependencyKey(dependencies...)
|
|
if !ok {
|
|
return zero, false
|
|
}
|
|
if mainObject.GetUID() == "" {
|
|
return zero, false
|
|
}
|
|
|
|
key := newResourceCacheKey(mainObject, desiredObject)
|
|
mainObjectRef := newResourceCacheObjectRef(mainObject)
|
|
resourceVersion := desiredObject.GetResourceVersion()
|
|
|
|
s.mu.RLock()
|
|
previous, ok := s.entries[key]
|
|
if ok && previous.MainObject == mainObjectRef && previous.ResourceVersion == resourceVersion && previous.dependencyKey.Equal(dependencyKey) {
|
|
s.mu.RUnlock()
|
|
return previous, false
|
|
}
|
|
s.mu.RUnlock()
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
previous, ok = s.entries[key]
|
|
if ok && previous.MainObject == mainObjectRef && previous.ResourceVersion == resourceVersion && previous.dependencyKey.Equal(dependencyKey) {
|
|
return previous, false
|
|
}
|
|
|
|
value := ResourceCacheValue[T]{
|
|
MainObject: mainObjectRef,
|
|
ResourceVersion: resourceVersion,
|
|
dependencyKey: dependencyKey,
|
|
Object: desiredObject,
|
|
}
|
|
s.entries[key] = value
|
|
s.indexKeyLocked(key)
|
|
return value, true
|
|
}
|
|
|
|
func (c *ResourceCache) Delete(mainObject client.Object) {
|
|
if mainObject == nil {
|
|
return
|
|
}
|
|
|
|
c.autoscalingListener.Delete(mainObject)
|
|
c.ephemeralRunnerSet.Delete(mainObject)
|
|
c.listenerPod.Delete(mainObject)
|
|
c.listenerServiceAccount.Delete(mainObject)
|
|
c.listenerRole.Delete(mainObject)
|
|
c.listenerRoleBinding.Delete(mainObject)
|
|
}
|
|
|
|
func (s *resourceCacheState[T]) Delete(mainObject client.Object) {
|
|
if s == nil || mainObject == nil {
|
|
return
|
|
}
|
|
|
|
uid := mainObject.GetUID()
|
|
if uid == "" {
|
|
return
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for key := range s.entriesByMainUID[uid] {
|
|
delete(s.entries, key)
|
|
}
|
|
delete(s.entriesByMainUID, uid)
|
|
}
|
|
|
|
func (s *resourceCacheState[T]) indexKeyLocked(key ResourceCacheKey) {
|
|
keys, ok := s.entriesByMainUID[key.MainUID]
|
|
if !ok {
|
|
keys = make(map[ResourceCacheKey]struct{}, resourceCacheInitialOwnerEntries)
|
|
s.entriesByMainUID[key.MainUID] = keys
|
|
}
|
|
keys[key] = struct{}{}
|
|
}
|
|
|
|
func newResourceCacheKey(mainObject client.Object, desiredObject client.Object) ResourceCacheKey {
|
|
return ResourceCacheKey{
|
|
MainUID: mainObject.GetUID(),
|
|
Namespace: desiredObject.GetNamespace(),
|
|
Name: resourceCacheObjectName(desiredObject),
|
|
}
|
|
}
|
|
|
|
func newResourceCacheDependencyKey(objects ...client.Object) (resourceCacheDependencyKey, bool) {
|
|
if len(objects) > resourceCacheMaxDependencyRefs {
|
|
return resourceCacheDependencyKey{}, false
|
|
}
|
|
|
|
key := resourceCacheDependencyKey{count: len(objects)}
|
|
for i, object := range objects {
|
|
if isNilResourceCacheObject(object) {
|
|
return resourceCacheDependencyKey{}, false
|
|
}
|
|
key.refs[i] = newResourceCacheObjectRef(object)
|
|
}
|
|
slices.SortFunc(key.refs[:key.count], func(a, b ResourceCacheObjectRef) int {
|
|
return compareResourceCacheObjectRefs(a, b)
|
|
})
|
|
return key, true
|
|
}
|
|
|
|
func (k resourceCacheDependencyKey) Equal(other resourceCacheDependencyKey) bool {
|
|
if k.count != other.count {
|
|
return false
|
|
}
|
|
if k.count > len(k.refs) || other.count > len(other.refs) {
|
|
return false
|
|
}
|
|
|
|
for i := range k.count {
|
|
if k.refs[i] != other.refs[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func newResourceCacheObjectRef(object client.Object) ResourceCacheObjectRef {
|
|
resourceVersion := object.GetResourceVersion()
|
|
if resourceVersion == "" {
|
|
resourceVersion = hash.ComputeTemplateHash(object)
|
|
}
|
|
|
|
return ResourceCacheObjectRef{
|
|
ObjectType: object.GetObjectKind().GroupVersionKind(),
|
|
Namespace: object.GetNamespace(),
|
|
Name: resourceCacheObjectName(object),
|
|
UID: object.GetUID(),
|
|
ResourceVersion: resourceVersion,
|
|
}
|
|
}
|
|
|
|
func compareResourceCacheObjectRefs(a, b ResourceCacheObjectRef) int {
|
|
if c := compareGroupVersionKinds(a.ObjectType, b.ObjectType); c != 0 {
|
|
return c
|
|
}
|
|
if c := strings.Compare(a.Namespace, b.Namespace); c != 0 {
|
|
return c
|
|
}
|
|
if c := strings.Compare(a.Name, b.Name); c != 0 {
|
|
return c
|
|
}
|
|
if c := strings.Compare(string(a.UID), string(b.UID)); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(a.ResourceVersion, b.ResourceVersion)
|
|
}
|
|
|
|
func compareGroupVersionKinds(a, b schema.GroupVersionKind) int {
|
|
if c := strings.Compare(a.Group, b.Group); c != 0 {
|
|
return c
|
|
}
|
|
if c := strings.Compare(a.Version, b.Version); c != 0 {
|
|
return c
|
|
}
|
|
return strings.Compare(a.Kind, b.Kind)
|
|
}
|
|
|
|
func resourceCacheObjectName(object client.Object) string {
|
|
if object.GetName() != "" {
|
|
return object.GetName()
|
|
}
|
|
return object.GetGenerateName()
|
|
}
|
|
|
|
func isNilResourceCacheObject[T client.Object](object T) bool {
|
|
var clientObject client.Object = object
|
|
if clientObject == nil {
|
|
return true
|
|
}
|
|
|
|
value := reflect.ValueOf(clientObject)
|
|
return value.Kind() == reflect.Pointer && value.IsNil()
|
|
}
|