-
Notifications
You must be signed in to change notification settings - Fork 24
Add ConfigMaps discovery and reporting support #754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8fc4343
4698ff0
d278758
a052007
ca6480f
0131500
aa23448
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,6 +77,18 @@ type ConfigDynamic struct { | |
| IncludeNamespaces []string `yaml:"include-namespaces"` | ||
| // FieldSelectors is a list of field selectors to use when listing this resource | ||
| FieldSelectors []string `yaml:"field-selectors"` | ||
| // IncludeResourcesByLabels filters to include only resources that have all of the specified labels. | ||
| // This controls which resources are collected, not which labels are included. | ||
| IncludeResourcesByLabels map[string]string `yaml:"include-resources-by-labels"` | ||
| // ExcludeResourcesByLabels filters to exclude resources that have any of the specified labels. | ||
| // This controls which resources are collected, not which labels are excluded. | ||
| ExcludeResourcesByLabels map[string]string `yaml:"exclude-resources-by-labels"` | ||
| // IncludeResourcesByAnnotations filters to include only resources that have all of the specified annotations. | ||
| // This controls which resources are collected, not which annotations are included. | ||
| IncludeResourcesByAnnotations map[string]string `yaml:"include-resources-by-annotations"` | ||
| // ExcludeResourcesByAnnotations filters to exclude resources that have any of the specified annotations. | ||
| // This controls which resources are collected, not which annotations are excluded. | ||
| ExcludeResourcesByAnnotations map[string]string `yaml:"exclude-resources-by-annotations"` | ||
| } | ||
|
|
||
| // UnmarshalYAML unmarshals the ConfigDynamic resolving GroupVersionResource. | ||
|
|
@@ -88,9 +100,13 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(any) error) error { | |
| Version string `yaml:"version"` | ||
| Resource string `yaml:"resource"` | ||
| } `yaml:"resource-type"` | ||
| ExcludeNamespaces []string `yaml:"exclude-namespaces"` | ||
| IncludeNamespaces []string `yaml:"include-namespaces"` | ||
| FieldSelectors []string `yaml:"field-selectors"` | ||
| ExcludeNamespaces []string `yaml:"exclude-namespaces"` | ||
| IncludeNamespaces []string `yaml:"include-namespaces"` | ||
| FieldSelectors []string `yaml:"field-selectors"` | ||
| IncludeResourcesByLabels map[string]string `yaml:"include-resources-by-labels"` | ||
| ExcludeResourcesByLabels map[string]string `yaml:"exclude-resources-by-labels"` | ||
| IncludeResourcesByAnnotations map[string]string `yaml:"include-resources-by-annotations"` | ||
| ExcludeResourcesByAnnotations map[string]string `yaml:"exclude-resources-by-annotations"` | ||
| }{} | ||
| err := unmarshal(&aux) | ||
| if err != nil { | ||
|
|
@@ -104,6 +120,10 @@ func (c *ConfigDynamic) UnmarshalYAML(unmarshal func(any) error) error { | |
| c.ExcludeNamespaces = aux.ExcludeNamespaces | ||
| c.IncludeNamespaces = aux.IncludeNamespaces | ||
| c.FieldSelectors = aux.FieldSelectors | ||
| c.IncludeResourcesByLabels = aux.IncludeResourcesByLabels | ||
| c.ExcludeResourcesByLabels = aux.ExcludeResourcesByLabels | ||
| c.IncludeResourcesByAnnotations = aux.IncludeResourcesByAnnotations | ||
| c.ExcludeResourcesByAnnotations = aux.ExcludeResourcesByAnnotations | ||
|
|
||
| return nil | ||
| } | ||
|
|
@@ -115,6 +135,14 @@ func (c *ConfigDynamic) validate() error { | |
| errs = append(errs, "cannot set excluded and included namespaces") | ||
| } | ||
|
|
||
| if len(c.ExcludeResourcesByLabels) > 0 && len(c.IncludeResourcesByLabels) > 0 { | ||
| errs = append(errs, "cannot use both include-resources-by-labels and exclude-resources-by-labels") | ||
| } | ||
|
|
||
| if len(c.ExcludeResourcesByAnnotations) > 0 && len(c.IncludeResourcesByAnnotations) > 0 { | ||
| errs = append(errs, "cannot use both include-resources-by-annotations and exclude-resources-by-annotations") | ||
| } | ||
|
|
||
| if c.GroupVersionResource.Resource == "" { | ||
| errs = append(errs, "invalid configuration: GroupVersionResource.Resource cannot be empty") | ||
| } | ||
|
|
@@ -145,6 +173,9 @@ var kubernetesNativeResources = map[schema.GroupVersionResource]sharedInformerFu | |
| corev1.SchemeGroupVersion.WithResource("pods"): func(sharedFactory informers.SharedInformerFactory) k8scache.SharedIndexInformer { | ||
| return sharedFactory.Core().V1().Pods().Informer() | ||
| }, | ||
| corev1.SchemeGroupVersion.WithResource("configmaps"): func(sharedFactory informers.SharedInformerFactory) k8scache.SharedIndexInformer { | ||
| return sharedFactory.Core().V1().ConfigMaps().Informer() | ||
| }, | ||
| corev1.SchemeGroupVersion.WithResource("nodes"): func(sharedFactory informers.SharedInformerFactory) k8scache.SharedIndexInformer { | ||
| return sharedFactory.Core().V1().Nodes().Informer() | ||
| }, | ||
|
|
@@ -219,6 +250,10 @@ func (c *ConfigDynamic) newDataGathererWithClient(ctx context.Context, cl dynami | |
| fieldSelector: fieldSelector.String(), | ||
| namespaces: c.IncludeNamespaces, | ||
| cache: dgCache, | ||
| includeLabels: c.IncludeResourcesByLabels, | ||
| excludeLabels: c.ExcludeResourcesByLabels, | ||
| includeAnnotations: c.IncludeResourcesByAnnotations, | ||
| excludeAnnotations: c.ExcludeResourcesByAnnotations, | ||
| } | ||
|
|
||
| // In order to reduce memory usage that might come from using Dynamic Informers | ||
|
|
@@ -302,6 +337,13 @@ type DataGathererDynamic struct { | |
|
|
||
| ExcludeAnnotKeys []*regexp.Regexp | ||
| ExcludeLabelKeys []*regexp.Regexp | ||
|
|
||
| // includeLabels and excludeLabels filter resources based on their labels | ||
| includeLabels map[string]string | ||
| excludeLabels map[string]string | ||
| // includeAnnotations and excludeAnnotations filter resources based on their annotations | ||
| includeAnnotations map[string]string | ||
| excludeAnnotations map[string]string | ||
| } | ||
|
|
||
| // Run starts the dynamic data gatherer's informers for resource collection. | ||
|
|
@@ -367,9 +409,23 @@ func (g *DataGathererDynamic) Fetch() (any, int, error) { | |
| cacheObject := item.Object.(*api.GatheredResource) | ||
| if resource, ok := cacheObject.Resource.(cacheResource); ok { | ||
| namespace := resource.GetNamespace() | ||
| if isIncludedNamespace(namespace, fetchNamespaces) { | ||
| items = append(items, cacheObject) | ||
| if !isIncludedNamespace(namespace, fetchNamespaces) { | ||
| continue | ||
| } | ||
|
|
||
| // filter by labels | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assumption is that labels will be used more broadly, so they are applied for filtering first, followed by annotations. Please share your thoughts if you have a different perspective. |
||
| labels := resource.GetLabels() | ||
| if !matchesFilter(labels, g.includeLabels, g.excludeLabels) { | ||
| continue | ||
| } | ||
|
|
||
| // filter by annotations | ||
| annotations := resource.GetAnnotations() | ||
| if !matchesFilter(annotations, g.includeAnnotations, g.excludeAnnotations) { | ||
| continue | ||
| } | ||
|
|
||
| items = append(items, cacheObject) | ||
| continue | ||
| } | ||
| return nil, -1, fmt.Errorf("failed to parse cached resource") | ||
|
|
@@ -563,6 +619,43 @@ func isIncludedNamespace(namespace string, namespaces []string) bool { | |
| return slices.Contains(namespaces, namespace) | ||
| } | ||
|
|
||
| // matchesFilter checks if the resource metadata (labels or annotations) match the include/exclude filters. | ||
| // If includeFilters is set, all key-value pairs must match for the resource to be included. | ||
| // An empty string value means "match any value for this key" (key-only matching). | ||
| // If excludeFilters is set, any matching key-value pair will exclude the resource. | ||
| func matchesFilter(resourceMetadata, includeFilters, excludeFilters map[string]string) bool { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regex support is not available, as I thought it was not required. Please share your thoughts if you have a different opinion. |
||
| // Check exclude filters first | ||
| if len(excludeFilters) > 0 { | ||
| for key, value := range excludeFilters { | ||
| if resourceValue, exists := resourceMetadata[key]; exists { | ||
| // If exclude value is empty, exclude any resource with this key | ||
| // Otherwise, only exclude if the value also matches | ||
| if value == "" || resourceValue == value { | ||
| return false | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Check include filters | ||
| if len(includeFilters) > 0 { | ||
| for key, value := range includeFilters { | ||
| resourceValue, exists := resourceMetadata[key] | ||
| if !exists { | ||
| // Required key is missing, filter it out | ||
| return false | ||
| } | ||
| // If include value is empty, we only care that the key exists | ||
| // Otherwise, the value must also match | ||
| if value != "" && resourceValue != value { | ||
| return false | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| func isNativeResource(gvr schema.GroupVersionResource) bool { | ||
| _, ok := kubernetesNativeResources[gvr] | ||
| return ok | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This label uniquely identifies the ConfigMaps used by the Secrets Manager (aka Conjur) components.