Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ require (
k8s.io/klog/v2 v2.130.1
k8s.io/kube-aggregator v0.35.1
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0
)

require (
Expand All @@ -43,6 +45,7 @@ require (
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect
github.com/go-openapi/jsonreference v0.21.2 // indirect
github.com/go-openapi/swag v0.25.1 // indirect
Expand All @@ -62,6 +65,7 @@ require (
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand Down Expand Up @@ -98,9 +102,9 @@ require (
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
sigs.k8s.io/controller-runtime v0.22.2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20241205171354-8006f302fd12
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
Expand Down Expand Up @@ -68,6 +70,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
Expand Down Expand Up @@ -199,6 +203,9 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnf
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down Expand Up @@ -226,6 +233,10 @@ sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5
sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 h1:PFWFSkpArPNJxFX4ZKWAk9NSeRoZaXschn+ULa4xVek=
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96/go.mod h1:EOBQyBowOUsd7U4CJnMHNE0ri+zCXyouGdLwC/jZU+I=
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
Expand Down
1 change: 1 addition & 0 deletions hack/generate-lib-resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ def scheme_group_versions(types):
modifiers = {
('k8s.io/api/apps/v1', 'Deployment'): 'b.modifyDeployment',
('k8s.io/api/apps/v1', 'DaemonSet'): 'b.modifyDaemonSet',
('k8s.io/api/core/v1', 'ConfigMap'): 'b.modifyConfigMap',
}

health_checks = {
Expand Down
223 changes: 223 additions & 0 deletions lib/resourcebuilder/core.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package resourcebuilder
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the existing logic, I am proposing to add small tests for:

TestModifyConfigMap

  • ConfigMap with invalid YAML - no modification
    • to check explicitly YAML parsing errors
  • ConfigMap with wrong apiVersion - no modification

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the PR tomorrow


import (
"context"
"errors"
"fmt"
"sort"

"sigs.k8s.io/kustomize/kyaml/yaml"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"k8s.io/utils/clock"

configv1 "github.com/openshift/api/config/v1"
operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1"
configclientv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
"github.com/openshift/library-go/pkg/operator/configobserver/apiserver"
"github.com/openshift/library-go/pkg/operator/events"
"github.com/openshift/library-go/pkg/operator/resourcesynccontroller"
)

const (
// ConfigMapInjectTLSAnnotation is the annotation key that triggers TLS injection into ConfigMaps
ConfigMapInjectTLSAnnotation = "config.openshift.io/inject-tls"
)

type optional[T any] struct {
value T
found bool
}

type tlsConfig struct {
minTLSVersion optional[string]
cipherSuites optional[[]string]
}

func (b *builder) modifyConfigMap(ctx context.Context, cm *corev1.ConfigMap) error {
// Check for TLS injection annotation
if value, ok := cm.Annotations[ConfigMapInjectTLSAnnotation]; !ok || value != "true" {
return nil
}

klog.V(2).Infof("ConfigMap %s/%s has %s annotation set to true", cm.Namespace, cm.Name, ConfigMapInjectTLSAnnotation)

// Empty data, nothing to inject into
if cm.Data == nil {
klog.V(2).Infof("ConfigMap %s/%s has empty data, skipping TLS profile injection", cm.Namespace, cm.Name)
return nil
}

// Observe TLS configuration from APIServer
tlsConf, err := b.observeTLSConfiguration(ctx, cm)
if err != nil {
return fmt.Errorf("unable to observe TLS configuration: %v", err)
}

minTLSLog := "<not found>"
if tlsConf.minTLSVersion.found {
minTLSLog = tlsConf.minTLSVersion.value
}
cipherSuitesLog := "<not found>"
if tlsConf.cipherSuites.found {
cipherSuitesLog = fmt.Sprintf("%v", tlsConf.cipherSuites.value)
}
klog.V(4).Infof("ConfigMap %s/%s: observed minTLSVersion=%v, cipherSuites=%v",
cm.Namespace, cm.Name, minTLSLog, cipherSuitesLog)

// Process each data entry that contains GenericOperatorConfig
for key, value := range cm.Data {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was supposed to be a comment about the need to document this functionality in the enhancement repository. However, you already thought of this on Slack, awesome! I will have a look for a right page. I am leaving this comment for me to remember this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we can create a new document in https://github.com/openshift/enhancements/tree/master/dev-guide/cluster-version-operator/dev, as this will not be an enhancement per se, but rather a dev-guide documentation explaining the usage and any constraints.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

klog.V(4).Infof("Processing %q key", key)
// Parse YAML into RNode to preserve formatting and field order
rnode, err := yaml.Parse(value)
if err != nil {
klog.V(4).Infof("ConfigMap's %q entry parsing failed: %v", key, err)
// Not valid YAML, skip this entry
continue
}

// Check if this is a supported config kind
switch {
case rnode.GetKind() == "GenericOperatorConfig" && rnode.GetApiVersion() == operatorv1alpha1.GroupVersion.String():
case rnode.GetKind() == "GenericControllerConfig" && rnode.GetApiVersion() == configv1.GroupVersion.String():
default:
klog.V(4).Infof("ConfigMap's %q entry is not a supported config type. Only GenericOperatorConfig (%v) and GenericControllerConfig (%v) are. Skipping this entry", key, operatorv1alpha1.GroupVersion.String(), configv1.GroupVersion.String())
continue
}

klog.V(2).Infof("ConfigMap %s/%s processing GenericOperatorConfig in key %s", cm.Namespace, cm.Name, key)

// Inject TLS settings into the GenericOperatorConfig while preserving structure
if err := updateRNodeWithTLSSettings(rnode, tlsConf); err != nil {
return fmt.Errorf("failed to inject the TLS configuration: %v", err)
}

// Marshal the modified RNode back to YAML
modifiedYAML, err := rnode.String()
if err != nil {
return fmt.Errorf("failed to marshall the modified ConfigMap back to YAML: %v", err)
}

// Update the ConfigMap data entry with the modified YAML
cm.Data[key] = modifiedYAML
klog.V(2).Infof("ConfigMap %s/%s updated GenericOperatorConfig with TLS profile in key %s", cm.Namespace, cm.Name, key)
}
return nil
}

// observeTLSConfiguration retrieves TLS configuration from the APIServer cluster CR
// using ObserveTLSSecurityProfile and extracts minTLSVersion and cipherSuites.
func (b *builder) observeTLSConfiguration(ctx context.Context, cm *corev1.ConfigMap) (*tlsConfig, error) {
// Create a lister adapter for ObserveTLSSecurityProfile
lister := &apiServerListerAdapter{
client: b.configClientv1.APIServers(),
ctx: ctx,
}
listers := &configObserverListers{
apiServerLister: lister,
}

// Create an in-memory event recorder that doesn't send events to the API server
recorder := events.NewInMemoryRecorder("configmap-tls-injection", clock.RealClock{})

// Call ObserveTLSSecurityProfile to get TLS configuration
observedConfig, errs := apiserver.ObserveTLSSecurityProfile(listers, recorder, map[string]any{})
if len(errs) > 0 {
return nil, fmt.Errorf("error observing TLS profile for ConfigMap %s/%s: %w", cm.Namespace, cm.Name, errors.Join(errs...))
}

config := &tlsConfig{}

// Extract minTLSVersion from the observed config
if minTLSVersion, minTLSFound, err := unstructured.NestedString(observedConfig, "servingInfo", "minTLSVersion"); err != nil {
return nil, err
} else if minTLSFound {
config.minTLSVersion = optional[string]{value: minTLSVersion, found: true}
}

// Extract cipherSuites from the observed config
if cipherSuites, ciphersFound, err := unstructured.NestedStringSlice(observedConfig, "servingInfo", "cipherSuites"); err != nil {
return nil, err
} else if ciphersFound {
// Sort cipher suites for consistent ordering
sort.Strings(cipherSuites)
config.cipherSuites = optional[[]string]{value: cipherSuites, found: true}
}

return config, nil
}

// updateRNodeWithTLSSettings injects TLS settings into a GenericOperatorConfig RNode while preserving structure.
// If a field in tlsConf is not found, the corresponding field will be deleted from the RNode.
func updateRNodeWithTLSSettings(rnode *yaml.RNode, tlsConf *tlsConfig) error {
servingInfo, err := rnode.Pipe(yaml.LookupCreate(yaml.MappingNode, "servingInfo"))
Copy link
Copy Markdown
Contributor

@DavidHurta DavidHurta Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have seen your comment.

Feel free to add such logic. Something like this should hopefully work:

Suggested change
servingInfo, err := rnode.Pipe(yaml.LookupCreate(yaml.MappingNode, "servingInfo"))
// If neither field is found, check if servingInfo exists
if !tlsConf.cipherSuites.found && !tlsConf.minTLSVersion.found {
// Only proceed if servingInfo already exists (to clear fields)
servingInfo, err := rnode.Pipe(yaml.Lookup("servingInfo"))
if err != nil {
return err
}
// servingInfo doesn't exist, nothing to do
if servingInfo == nil {
return nil
}
// servingInfo exists, clear both fields
if err := servingInfo.PipeE(yaml.Clear("cipherSuites")); err != nil {
return err
}
if err := servingInfo.PipeE(yaml.Clear("minTLSVersion")); err != nil {
return err
}
return nil
}
// At least one field is found, so create servingInfo if needed
servingInfo, err := rnode.Pipe(yaml.LookupCreate(yaml.MappingNode, "servingInfo"))

with the updated unit test and a new test case/s for this new logic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not aware of the impact of the existing logic. If it's a no-op, we can leave it as is due to time constraints. If we are not certain, we can implement the logic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor issue. Given both fields are always present this is no-op. Please ignore.

if err != nil {
return err
}

// Handle cipherSuites field
if tlsConf.cipherSuites.found {
seqNode := yaml.NewListRNode(tlsConf.cipherSuites.value...)
if err := servingInfo.PipeE(yaml.SetField("cipherSuites", seqNode)); err != nil {
return err
}
} else {
if err := servingInfo.PipeE(yaml.Clear("cipherSuites")); err != nil {
return err
}
}

// Handle minTLSVersion field
if tlsConf.minTLSVersion.found {
if err := servingInfo.PipeE(yaml.SetField("minTLSVersion", yaml.NewStringRNode(tlsConf.minTLSVersion.value))); err != nil {
return err
}
} else {
if err := servingInfo.PipeE(yaml.Clear("minTLSVersion")); err != nil {
return err
}
}

return nil
}

// apiServerListerAdapter adapts a client interface to the lister interface
type apiServerListerAdapter struct {
client configclientv1.APIServerInterface
ctx context.Context
}

func (a *apiServerListerAdapter) List(selector labels.Selector) ([]*configv1.APIServer, error) {
// Not implemented - ObserveTLSSecurityProfile only uses Get()
return nil, nil
}

func (a *apiServerListerAdapter) Get(name string) (*configv1.APIServer, error) {
return a.client.Get(a.ctx, name, metav1.GetOptions{})
}

// configObserverListers implements the configobserver.Listers interface.
// It's expected to be used solely for apiserver.ObserveTLSSecurityProfile.
type configObserverListers struct {
apiServerLister configlistersv1.APIServerLister
}

func (l *configObserverListers) APIServerLister() configlistersv1.APIServerLister {
return l.apiServerLister
}

func (l *configObserverListers) ResourceSyncer() resourcesynccontroller.ResourceSyncer {
// Not needed for TLS observation
return nil
}

func (l *configObserverListers) PreRunHasSynced() []cache.InformerSynced {
// Not needed for TLS observation
return nil
}
Comment on lines +215 to +223
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the repo structure and find the relevant files
git ls-files | grep -E "(resourcebuilder|configobserver)" | head -20

Repository: openshift/cluster-version-operator

Length of output: 1188


🏁 Script executed:

# Read the core.go file to see the full context
cat -n lib/resourcebuilder/core.go | sed -n '220,240p'

# Also check the interface definition
cat -n lib/resourcebuilder/interface.go | head -50

# Look at the observe_tlssecurityprofile implementation
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go

Repository: openshift/cluster-version-operator

Length of output: 8005


🏁 Script executed:

# Find the APIServerLister interface definition
rg -n "type APIServerLister" --type go

# Search for all uses of ResourceSyncer in the codebase
rg -n "ResourceSyncer" --type go

# Search for all uses of PreRunHasSynced in the codebase
rg -n "PreRunHasSynced" --type go

# Find where configObserverListers is instantiated
rg -n "configObserverListers{" --type go

Repository: openshift/cluster-version-operator

Length of output: 2179


🏁 Script executed:

# Read the config_observer_controller.go around the lines that call PreRunHasSynced
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/config_observer_controller.go | sed -n '35,45p'

# Read the context around lines 255-257 where PreRunHasSynced is called
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/config_observer_controller.go | sed -n '250,270p'

# Look at the full Listers interface definition
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/config_observer_controller.go | sed -n '33,50p'

# Check the apiserver listers.go to understand the interface
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/listers.go

Repository: openshift/cluster-version-operator

Length of output: 3078


🏁 Script executed:

# Find where listersToInformer is called
rg -n "listersToInformer" --type go

# Look for the Run method or startup code that uses this
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/config_observer_controller.go | sed -n '200,250p'

# Check how configObserverListers is initialized and used in lib/resourcebuilder/core.go
cat -n lib/resourcebuilder/core.go | sed -n '1,50p'

# Look for where configObserverListers is created/instantiated
rg -n "New.*configObserverListers\|&configObserverListers" --type go

Repository: openshift/cluster-version-operator

Length of output: 6154


🏁 Script executed:

# Look at the code around line 126 in config_observer_controller.go
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/config_observer_controller.go | sed -n '100,140p'

# Search for any calls to ResourceSyncer() in the codebase
rg -n "\.ResourceSyncer\(\)" --type go -A 2

# Check if the configObserverListers is actually used with the config observer controller
grep -r "configObserverListers\|NewConfigObserverListers" lib/ --include="*.go"

Repository: openshift/cluster-version-operator

Length of output: 2578


🏁 Script executed:

# Find where configObserverListers is used
rg -n "configObserverListers" lib/resourcebuilder/core.go -B 5 -A 10

# Check if it's passed to NewNestedConfigObserver or similar
rg -n "NewNestedConfigObserver\|NewConfigObserver" lib/ --include="*.go" -B 2 -A 2

Repository: openshift/cluster-version-operator

Length of output: 2026


🏁 Script executed:

# Double-check the current usage is safe by seeing what ObserveTLSSecurityProfile actually does
cat -n vendor/github.com/openshift/library-go/pkg/operator/configobserver/apiserver/observe_tlssecurityprofile.go | sed -n '35,80p'

Repository: openshift/cluster-version-operator

Length of output: 2460


Return proper implementations instead of nil for ResourceSyncer() and PreRunHasSynced(), or document that configObserverListers must never be passed to code paths that invoke these methods.

ObserveTLSSecurityProfile currently only calls APIServerLister() and never invokes ResourceSyncer() or PreRunHasSynced(), so the current usage is safe. However, configObserverListers implements the configobserver.Listers interface, which requires both methods. The listersToInformer() function in library-go's config_observer_controller.go (lines 255–257) iterates over the result of PreRunHasSynced() without nil-checking, which would panic if this lister is ever passed to NewNestedConfigObserver() or similar code paths. This pattern violates the interface contract and creates a landmine for future refactoring. Either return empty slices/objects instead of nil, or add a clear comment that this lister is only for TLS observation and must never be used with the config observer controller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/resourcebuilder/core.go` around lines 224 - 232, configObserverListers
currently returns nil from ResourceSyncer() and PreRunHasSynced(), violating the
configobserver.Listers contract and risking a panic in callers like
listersToInformer() or NewNestedConfigObserver(); fix by returning concrete
no-op implementations: have PreRunHasSynced() return an empty slice (e.g. return
[]cache.InformerSynced{}), and implement a small noop ResourceSyncer type that
satisfies resourcesynccontroller.ResourceSyncer (methods are no-ops) and return
an instance from ResourceSyncer(); update core.go's configObserverListers
methods accordingly (referencing configObserverListers, ResourceSyncer(),
PreRunHasSynced(), ObserveTLSSecurityProfile, APIServerLister(),
listersToInformer(), NewNestedConfigObserver()) or, if you choose not to add a
noop, add a clear comment on configObserverListers' type header stating it must
never be passed to code paths that call these methods.

Loading