package configmanager import ( "fmt" "sort" "sync/atomic" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" "github.com/karmada-io/karmada/pkg/util/helper" "github.com/karmada-io/karmada/pkg/util/informermanager" ) var resourceExploringWebhookConfigurationsGVR = schema.GroupVersionResource{ Group: configv1alpha1.GroupVersion.Group, Version: configv1alpha1.GroupVersion.Version, Resource: "resourceinterpreterwebhookconfigurations", } // ConfigManager can list dynamic webhooks. type ConfigManager interface { HookAccessors() []WebhookAccessor HasSynced() bool } // interpreterConfigManager collect the resource interpreter webhook configuration. type interpreterConfigManager struct { configuration *atomic.Value lister cache.GenericLister initialSynced *atomic.Value } // HookAccessors return all configured resource interpreter webhook. func (m *interpreterConfigManager) HookAccessors() []WebhookAccessor { return m.configuration.Load().([]WebhookAccessor) } // HasSynced return true when the manager is synced with existing configuration. func (m *interpreterConfigManager) HasSynced() bool { if m.initialSynced.Load().(bool) { return true } if configuration, err := m.lister.List(labels.Everything()); err == nil && len(configuration) == 0 { // the empty list we initially stored is valid to use. // Setting initialSynced to true, so subsequent checks // would be able to take the fast path on the atomic boolean in a // cluster without any webhooks configured. m.initialSynced.Store(true) // the informer has synced, and we don't have any items return true } return false } // NewExploreConfigManager return a new interpreterConfigManager with resourceinterpreterwebhookconfigurations handlers. func NewExploreConfigManager(inform informermanager.SingleClusterInformerManager) ConfigManager { manager := &interpreterConfigManager{ configuration: &atomic.Value{}, lister: inform.Lister(resourceExploringWebhookConfigurationsGVR), initialSynced: &atomic.Value{}, } manager.configuration.Store([]WebhookAccessor{}) manager.initialSynced.Store(false) configHandlers := informermanager.NewHandlerOnEvents( func(_ interface{}) { manager.updateConfiguration() }, func(_, _ interface{}) { manager.updateConfiguration() }, func(_ interface{}) { manager.updateConfiguration() }) inform.ForResource(resourceExploringWebhookConfigurationsGVR, configHandlers) return manager } func (m *interpreterConfigManager) updateConfiguration() { configurations, err := m.lister.List(labels.Everything()) if err != nil { utilruntime.HandleError(fmt.Errorf("error updating configuration: %v", err)) return } configs := make([]*configv1alpha1.ResourceInterpreterWebhookConfiguration, 0) for _, c := range configurations { unstructuredConfig, err := helper.ToUnstructured(c) if err != nil { klog.Errorf("Failed to transform ResourceInterpreterWebhookConfiguration: %w", err) return } config, err := helper.ConvertToResourceExploringWebhookConfiguration(unstructuredConfig) if err != nil { gvk := unstructuredConfig.GroupVersionKind().String() klog.Errorf("Failed to convert object(%s), err: %v", gvk, err) return } configs = append(configs, config) } m.configuration.Store(mergeResourceExploreWebhookConfigurations(configs)) m.initialSynced.Store(true) } func mergeResourceExploreWebhookConfigurations(configurations []*configv1alpha1.ResourceInterpreterWebhookConfiguration) []WebhookAccessor { sort.SliceStable(configurations, func(i, j int) bool { return configurations[i].Name < configurations[j].Name }) var accessors []WebhookAccessor for ci, config := range configurations { for hi, hook := range config.Webhooks { uid := fmt.Sprintf("%s/%s", config.Name, hook.Name) accessors = append(accessors, NewResourceExploringAccessor(uid, config.Name, &configurations[ci].Webhooks[hi])) } } return accessors }