Files

231 lines
6.4 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/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var resourceCacheObjectTypes sync.Map
type ResourceCacheObjectRef struct {
ObjectType string
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
Dependencies []ResourceCacheObjectRef
Object T
}
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]
}
func newResourceCacheState[T client.Object]() *resourceCacheState[T] {
return &resourceCacheState[T]{
entries: make(map[ResourceCacheKey]ResourceCacheValue[T], 512),
}
}
func (s *resourceCacheState[T]) Get(
mainObject client.Object,
desiredObject T,
dependencies ...client.Object,
) (T, bool) {
key := newResourceCacheKey(mainObject, desiredObject)
s.mu.RLock()
value, ok := s.entries[key]
s.mu.RUnlock()
if !ok || !value.Matches(mainObject, dependencies...) {
var zero T
return zero, false
}
return cloneResourceCacheObject(value.Object), true
}
func (s *resourceCacheState[T]) Upsert(
mainObject client.Object,
desiredObject T,
dependencies ...client.Object,
) (ResourceCacheValue[T], bool) {
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.dependenciesMatch(dependencies...) {
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.dependenciesMatch(dependencies...) {
return previous, false
}
dependencyRefs := newResourceCacheObjectRefs(dependencies...)
value := newResourceCacheValue(mainObjectRef, resourceVersion, dependencyRefs, cloneResourceCacheObject(desiredObject))
s.entries[key] = value
return value, true
}
func (v ResourceCacheValue[T]) Matches(mainObject client.Object, dependencies ...client.Object) bool {
if v.MainObject != newResourceCacheObjectRef(mainObject) {
return false
}
return v.dependenciesMatch(dependencies...)
}
func newResourceCacheKey(mainObject client.Object, desiredObject client.Object) ResourceCacheKey {
return ResourceCacheKey{
MainUID: mainObject.GetUID(),
Namespace: desiredObject.GetNamespace(),
Name: resourceCacheObjectName(desiredObject),
}
}
func newResourceCacheValue[T client.Object](
mainObjectRef ResourceCacheObjectRef,
resourceVersion string,
dependencyRefs []ResourceCacheObjectRef,
object T,
) ResourceCacheValue[T] {
return ResourceCacheValue[T]{
MainObject: mainObjectRef,
ResourceVersion: resourceVersion,
Dependencies: dependencyRefs,
Object: object,
}
}
func cloneResourceCacheObject[T client.Object](object T) T {
return object.DeepCopyObject().(T)
}
func newResourceCacheObjectRefs(objects ...client.Object) []ResourceCacheObjectRef {
refs := make([]ResourceCacheObjectRef, 0, len(objects))
for _, object := range objects {
refs = append(refs, newResourceCacheObjectRef(object))
}
slices.SortFunc(refs, func(a, b ResourceCacheObjectRef) int {
return compareResourceCacheObjectRefs(a, b)
})
return refs
}
func (v ResourceCacheValue[T]) dependenciesMatch(objects ...client.Object) bool {
if len(v.Dependencies) != len(objects) {
return false
}
for _, object := range objects {
ref := newResourceCacheObjectRef(object)
if !slices.Contains(v.Dependencies, ref) {
return false
}
}
return true
}
func newResourceCacheObjectRef(object client.Object) ResourceCacheObjectRef {
resourceVersion := object.GetResourceVersion()
if resourceVersion == "" {
resourceVersion = hash.ComputeTemplateHash(object)
}
return ResourceCacheObjectRef{
ObjectType: resourceCacheObjectType(object),
Namespace: object.GetNamespace(),
Name: resourceCacheObjectName(object),
UID: object.GetUID(),
ResourceVersion: resourceVersion,
}
}
func compareResourceCacheObjectRefs(a, b ResourceCacheObjectRef) int {
if c := strings.Compare(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 resourceCacheObjectType(object client.Object) string {
t := reflect.TypeOf(object)
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if objectType, ok := resourceCacheObjectTypes.Load(t); ok {
return objectType.(string)
}
objectType := t.PkgPath() + "." + t.Name()
actual, _ := resourceCacheObjectTypes.LoadOrStore(t, objectType)
return actual.(string)
}
func resourceCacheObjectName(object client.Object) string {
if object.GetName() != "" {
return object.GetName()
}
return object.GetGenerateName()
}