Skip to content

Conversation

@pkoutsovasilis
Copy link
Contributor

@pkoutsovasilis pkoutsovasilis commented Nov 20, 2025

Overview

This PR implements support for multiple StackConfigPolicies (SCPs) targeting the same Elasticsearch cluster or Kibana instance, using a weight-based priority system for deterministic policy composition.

Key Features

Weight-Based Priority System

  • Policies are merged in order of weight (lower weight takes precedence)
  • Default weight: 0

Conflict Detection

Conflicts are detected across multiple dimensions and will prevent policy application:

Conflict Type Condition Result
Weight Conflict Two or more policies with identical weights target the same Elasticsearch/Kibana ❌ Conflict
SecretMount Name Conflict Different policies define SecretMount with same SecretName ❌ Conflict
SecretMount Path Conflict Different policies define SecretMount with same MountPath ❌ Conflict
Different Weights Policies have different weights and none of the above applies ✅ Pass - lower weight wins

Important: Even if two policies with the same weight have non-overlapping resources, they still conflict because the weight collision makes the merge order ambiguous.

Configuration Merging Behaviour

Different merge strategies are applied based on the configuration type:

  • Deep Merge (recursive merging):

    • ClusterSettings
    • Config
    • SnapshotLifecyclePolicies
    • SecurityRoleMappings
    • IndexLifecyclePolicies
    • IngestPipelines
    • IndexTemplates.ComposableIndexTemplates
    • IndexTemplates.ComponentTemplates
  • Top-Level Key Replacement (entire keys replaced):

    • SnapshotRepositories - each repository configuration is treated atomically
  • Union Merge (with conflict detection):

    • SecretMounts - conflicts on duplicate SecretName OR duplicate MountPath
    • SecureSettings - merges by SecretName+Key, lower weight wins (no conflicts)

Multi-Soft-Owner Secret Management

File Settings and Policy Config Secrets:

  • Now support multiple soft owners
  • Secrets are only deleted when all referencing soft-owners are removed
  • Uses eck.k8s.elastic.co/owner-refs annotation with JSON-encoded map of owner namespaced names

Secret Sources:

  • Remain single soft owner (existing behaviour unchanged)

This prevents secret leakage while enabling proper cleanup when policies are deleted.

Related Issues

@pkoutsovasilis pkoutsovasilis self-assigned this Nov 20, 2025
@pkoutsovasilis pkoutsovasilis added the >enhancement Enhancement of existing functionality label Nov 20, 2025
@prodsecmachine
Copy link
Collaborator

prodsecmachine commented Nov 20, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🔍 Preview links for changed docs

@kvalliyurnatt
Copy link
Contributor

Just a general question/comment that came to my mind, do we want to have any limits on the number of SCPs that can be associated to a cluster ?
I was just trying to think what potential issues we might face when we have a large number of SCPs(if that is even a practical scenario) associated with a single ES cluster and if there is a practical maximum that we can enforce. One thing that came to my mind while thinking about scale was the annotation for soft owners, maybe we run into some kind of limit with the annotation map size? (I think the annotation limit is 256KB which I think should not be something to worry about ?)
Wondering if there are any other such things to consider.

esPolicy.PoliciesWithConflictErrors[policyNsn] = err

if previouslyAppliedPolicy != nil {
previouslyAppliedPolicyNsn := k8s.ExtractNamespacedName(previouslyAppliedPolicy)
Copy link
Contributor

@barkbay barkbay Nov 24, 2025

Choose a reason for hiding this comment

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

I'm not sure I understand why we use previouslyAppliedPolicy here? The conflict could come from another policy applied before?

For example, assuming we have 3 policies (p1,p2,p3), processed in that order. Let's also assume p1 conflicts with p3 then IIUC we are going to report a conflict between p1 and p2? While the conflict is actually between p1 and p3?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like I should have reloaded the PR before posting my comments: #8917 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep you are both right, this conflict does not originate only from the previously applied policy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this in b5eaad2 and I believe now that the conflicting policies are captured correctly

Comment on lines 35 to 37
// PoliciesWithConflictErrors maps policy namespaced names to conflict errors when multiple policies
// have the same weight
PoliciesWithConflictErrors map[types.NamespacedName]error
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it only because they have the same weight? My understanding is that mergeElasticsearchConfig can also lead to policies being recorded here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the secretName collision came afterwards and I didn't update the godoc to include this reason as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have simplified the conflict handling logic in b5eaad2

if esPolicyConfigFinal == nil {
continue
}
conflictErr, exists := esPolicyConfigFinal.PoliciesWithConflictErrors[reconcilingPolicyNsn]
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's maybe add a comment to explain why using only err is not enough, and why we need to get conflictErr from esPolicyConfigFinal.PoliciesWithConflictErrors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed the behaviour in b5eaad2 and now I am using only err

Copy link
Collaborator

@pebrc pebrc left a comment

Choose a reason for hiding this comment

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

I did a first pass, just looking at the code. I have not tested it yet. Will try to find some more time later today.

ElasticsearchConfigAndSecretMountsHashAnnotation = "policy.k8s.elastic.co/elasticsearch-config-mounts-hash" //nolint:gosec
SourceSecretAnnotationName = "policy.k8s.elastic.co/source-secret-name" //nolint:gosec

SoftOwnerRefsAnnotation = "eck.k8s.elastic.co/owner-refs"
Copy link
Collaborator

@pebrc pebrc Nov 24, 2025

Choose a reason for hiding this comment

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

Should we keep it close to the existing labels used for soft owner refs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved this closer to the existing labels here dd18a2d

Comment on lines 84 to 88
// Process policies in order of weight (lowest first)
slices.Sort(weights)

// Reverse the weights so that we process policies in order of weight (highest first)
slices.Reverse(weights)
Copy link
Collaborator

Choose a reason for hiding this comment

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

	// Process policies in order of weight (highest first)
	slices.SortFunc(weights, func(a, b int32) int {
		return cmp.Compare(b, a) // Reversed comparison for descending order
	})

Yours is kind of easier to read ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2

// have the same weight
PoliciesWithConflictErrors map[types.NamespacedName]error
// PoliciesRefs contains all StackConfigPolicies that target this Kibana instance
PoliciesRefs []policyv1alpha1.StackConfigPolicy
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
PoliciesRefs []policyv1alpha1.StackConfigPolicy
PolicyRefs []policyv1alpha1.StackConfigPolicy

nit: double plural

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2

// have the same weight
PoliciesWithConflictErrors map[types.NamespacedName]error
// PoliciesRefs contains all StackConfigPolicies that target this Elasticsearch cluster
PoliciesRefs []policyv1alpha1.StackConfigPolicy
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
PoliciesRefs []policyv1alpha1.StackConfigPolicy
PolicyRefs []policyv1alpha1.StackConfigPolicy

nit: double plural

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2

Comment on lines 202 to 208
// doesPolicyRefsObject checks if the given StackConfigPolicy targets the given Elasticsearch cluster.
// A policy targets an Elasticsearch cluster if both following conditions are met:
// 1. The policy is in either the operator namespace or the same namespace as the Elasticsearch cluster
// 2. The policy's label selector matches the Elasticsearch cluster's labels
// Returns true or false depending on whether the given policy targets the Elasticsearch cluster and
// an error if the label selector is invalid.
func doesPolicyRefsObject(policy *policyv1alpha1.StackConfigPolicy, obj metav1.Object, operatorNamespace string) (bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// doesPolicyRefsObject checks if the given StackConfigPolicy targets the given Elasticsearch cluster.
// A policy targets an Elasticsearch cluster if both following conditions are met:
// 1. The policy is in either the operator namespace or the same namespace as the Elasticsearch cluster
// 2. The policy's label selector matches the Elasticsearch cluster's labels
// Returns true or false depending on whether the given policy targets the Elasticsearch cluster and
// an error if the label selector is invalid.
func doesPolicyRefsObject(policy *policyv1alpha1.StackConfigPolicy, obj metav1.Object, operatorNamespace string) (bool, error) {
// doesPolicyTargetObject checks if the given StackConfigPolicy targets the given object
// A policy targets an object if both following conditions are met:
// 1. The policy is in either the operator namespace or the same namespace as the object
// 2. The policy's label selector matches the objects's labels
// Returns true or false depending on whether the given policy targets the object and
// an error if the label selector is invalid.
func doesPolicyTargetObject(policy *policyv1alpha1.StackConfigPolicy, obj metav1.Object, operatorNamespace string) (bool, error) {

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or alternatively: doesPolicyMatchObject

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gonna go with doesPolicyMatchObject

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2

// Add all destination entries
for _, secureSetting := range dst {
secureSettings[secureSetting.SecretName] = make(map[string]commonv1.KeyToPath)
for _, entry := range secureSetting.Entries {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Entries is optional. If not specified all entries of the secret should be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2, no more special handling Secret sources, these are just getting merged as they come


// Group policies by weight
var weights []int32
weightKeyStackPolicies := make(map[int32][]*policyv1alpha1.StackConfigPolicy)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
weightKeyStackPolicies := make(map[int32][]*policyv1alpha1.StackConfigPolicy)
policiesByWeight := make(map[int32][]*policyv1alpha1.StackConfigPolicy)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed in b5eaad2

// create the expected Settings Secret
expectedSecret, expectedVersion, err := filesettings.NewSettingsSecretWithVersion(esNsn, &actualSettingsSecret, &policy, meta)
expectedSecret, expectedVersion, err := filesettings.NewSettingsSecretWithVersion(esNsn, &actualSettingsSecret, &policyv1alpha1.StackConfigPolicy{
ObjectMeta: reconcilingPolicy.ObjectMeta,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this not introduce a bug in that the secure settings sources will always be looked for in the namespace of reconciling policy? It needs to be specific to the source policy doesn't it? Also as we can have mulitple polices being reconciled will we not have an edit war over the namespace of the secret source?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes you are right this is a bug, I did miss the fact that we use also the policy namespace for the secure settings. thx for catching that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this in b5eaad2 to extract namespaced secret sources during the merging of all policies

// It processes all provided policies, filtering those that target the Elasticsearch cluster, and merges them
// in order of their weight (lowest to highest). Policies with the same weight are flagged as conflicts.
// Returns an esPolicyConfig containing the merged configuration and any error occurred during merging.
func getPolicyConfigForElasticsearch(es *esv1.Elasticsearch, allPolicies []policyv1alpha1.StackConfigPolicy, params operator.Parameters) (*esPolicyConfig, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is a lot of overlap with the Kibana version of this function. We could go with a generic version:

// Generic config holder
type policyConfig[T any] struct {
    Spec                       T
    PoliciesWithConflictErrors map[types.NamespacedName]error
    PoliciesRefs               []policyv1alpha1.StackConfigPolicy
}

// Generic merge function
func getPolicyConfigFor[T any](
    obj metav1.Object,  // Both ES and KB implement this
    allPolicies []policyv1alpha1.StackConfigPolicy,
    params operator.Parameters,
    extractSpec func(*policyv1alpha1.StackConfigPolicy) T,
    mergeSpec func(*T, T) error,
) (*policyConfig[T], error) {
    // ... unified implementation ...
}

// Usage:
esConfig, err := getPolicyConfigFor(
    es,
    allPolicies,
    params,
    func(p *policyv1alpha1.StackConfigPolicy) policyv1alpha1.ElasticsearchConfigPolicySpec {
        return p.Spec.Elasticsearch
    },
    mergeElasticsearchConfig,
)

kbConfig, err := getPolicyConfigFor(
    kb,
    allPolicies,
    params,
    func(p *policyv1alpha1.StackConfigPolicy) policyv1alpha1.KibanaConfigPolicySpec {
        return p.Spec.Kibana
    },
    mergeKibanaConfig,
)

Or we could maybe at least pull the shared bits into functions used from both?

Idk something like

func groupPoliciesByWeight(allPolicies []policyv1alpha1.StackConfigPolicy, filter func(*policyv1alpha1.StackConfigPolicy) bool) ([]int32, map[int32][]*policyv1alpha1.StackConfigPolicy, []policyv1alpha1.StackConfigPolicy)

func handleWeightConflicts[T any](config *policyConfig[T], policiesWithSameWeight []*policyv1alpha1.StackConfigPolicy, weight int32) error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did a refactor that follows the proposed generics approach in b5eaad2

switch {
case errors.Is(err, errMergeConflict):
log.V(1).Info("StackConfigPolicy merge conflict for Elasticsearch", "es_namespace", es.Namespace, "es_name", es.Name, "error", err)
results.WithRequeue(defaultRequeue)
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering why do we need to requeue here. Are we not expecting the user to fix the policy? (which would trigger a new reconciliation?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you are right, the requeue here is redundant and shouldn't happen, the expectation is what you say above, so I am gonna remove it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the redundant requeue in b5eaad2

log.V(1).Info("StackConfigPolicy merge conflict for Elasticsearch", "es_namespace", es.Namespace, "es_name", es.Name, "error", err)
results.WithRequeue(defaultRequeue)
if esPolicyConfigFinal == nil {
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

In what cases is this path (we have a errMergeConflict but esPolicyConfigFinal is nil) taken? And how the user can understand the above err if it is logged at the debug level?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in the current code there is no path that this combo of errMergeConflict and esPolicyConfigFinal is feasible, however esPolicyConfigFinal can be nil and should be checked, no?! Now this is a Info level log message so why you say debug?

Copy link
Contributor

Choose a reason for hiding this comment

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

Now this is a Info level log message so why you say debug?

https://github.com/elastic/cloud-on-k8s/blob/main/CONTRIBUTING.md#logging

We only use two levels: debug and info. To produce a log at the debug level use V(1) before the Info call:

logger.V(1).Info("starting reconciliation", "pod", req.NamespacedNamed)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh that explains it, thanks for the schooling

Copy link
Contributor

Choose a reason for hiding this comment

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

however esPolicyConfigFinal can be nil and should be checked, no?!

Not necessarily, you can use something like:

func (es *esPolicyConfig) HasErrorForPolicy(policyNsn types.NamespacedName) error {
	if es == nil {
		return nil
	}
	if err, exists := es.PoliciesWithConflictErrors[policyNsn]; exists {
		return err
	}
	return nil
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok what you propose still does the nil check but it confines everything in a esPolicyConfig struct func, which improves the code readability so more than happy to go with it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I simplified the merge conflict err handling in b5eaad2 so this is no longer necessary

Comment on lines +102 to +148
// GetElasticsearchNamespacedSecureSettings returns the Elasticsearch secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Elasticsearch secure settings defined.
func (p *StackConfigPolicy) GetElasticsearchNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}

ssLen := len(p.Spec.Elasticsearch.SecureSettings)
if ssLen == 0 {
return nil
}
pNs := p.GetNamespace()
ssNsn := make([]commonv1.NamespacedSecretSource, ssLen)
for idx, ss := range p.Spec.Elasticsearch.SecureSettings {
ssNsn[idx] = commonv1.NamespacedSecretSource{
Namespace: pNs,
SecretName: ss.SecretName,
Entries: ss.Entries,
}
}
return ssNsn
}

// GetKibanaNamespacedSecureSettings returns the Kibana secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Kibana secure settings defined.
func (p *StackConfigPolicy) GetKibanaNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}

ssLen := len(p.Spec.Kibana.SecureSettings)
if ssLen == 0 {
return nil
}
pNs := p.GetNamespace()
ssNsn := make([]commonv1.NamespacedSecretSource, ssLen)
for idx, ss := range p.Spec.Kibana.SecureSettings {
ssNsn[idx] = commonv1.NamespacedSecretSource{
Namespace: pNs,
SecretName: ss.SecretName,
Entries: ss.Entries,
}
}
return ssNsn
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, duplicated code/logic

Suggested change
// GetElasticsearchNamespacedSecureSettings returns the Elasticsearch secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Elasticsearch secure settings defined.
func (p *StackConfigPolicy) GetElasticsearchNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}
ssLen := len(p.Spec.Elasticsearch.SecureSettings)
if ssLen == 0 {
return nil
}
pNs := p.GetNamespace()
ssNsn := make([]commonv1.NamespacedSecretSource, ssLen)
for idx, ss := range p.Spec.Elasticsearch.SecureSettings {
ssNsn[idx] = commonv1.NamespacedSecretSource{
Namespace: pNs,
SecretName: ss.SecretName,
Entries: ss.Entries,
}
}
return ssNsn
}
// GetKibanaNamespacedSecureSettings returns the Kibana secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Kibana secure settings defined.
func (p *StackConfigPolicy) GetKibanaNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}
ssLen := len(p.Spec.Kibana.SecureSettings)
if ssLen == 0 {
return nil
}
pNs := p.GetNamespace()
ssNsn := make([]commonv1.NamespacedSecretSource, ssLen)
for idx, ss := range p.Spec.Kibana.SecureSettings {
ssNsn[idx] = commonv1.NamespacedSecretSource{
Namespace: pNs,
SecretName: ss.SecretName,
Entries: ss.Entries,
}
}
return ssNsn
}
// GetElasticsearchNamespacedSecureSettings returns the Elasticsearch secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Elasticsearch secure settings defined.
func (p *StackConfigPolicy) GetElasticsearchNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}
return toNamespacedSecretSources(&p.Spec.Elasticsearch, p.Namespace)
}
// GetKibanaNamespacedSecureSettings returns the Kibana secure settings from this policy
// as NamespacedSecretSources, with each secret source namespaced to the policy's namespace.
// Returns nil if the policy is nil or has no Kibana secure settings defined.
func (p *StackConfigPolicy) GetKibanaNamespacedSecureSettings() []commonv1.NamespacedSecretSource {
if p == nil {
return nil
}
return toNamespacedSecretSources(&p.Spec.Kibana, p.Namespace)
}
// HasSecureSettings represents a ConfigPolicySpec that has secure settings.
type HasSecureSettings interface {
GetSecureSettings() []commonv1.SecretSource
}
func toNamespacedSecretSources(hasSecureSettings HasSecureSettings, inNamespace string) []commonv1.NamespacedSecretSource {
secureSettings := hasSecureSettings.GetSecureSettings()
namespacedSecretSources := make([]commonv1.NamespacedSecretSource, len(secureSettings))
for i, s := range secureSettings {
namespacedSecretSources[i] = commonv1.NamespacedSecretSource{
Namespace: inNamespace,
SecretName: s.SecretName,
Entries: s.Entries,
}
}
return namespacedSecretSources
}
func (e *ElasticsearchConfigPolicySpec) GetSecureSettings() []commonv1.SecretSource {
return e.SecureSettings
}
func (k *KibanaConfigPolicySpec) GetSecureSettings() []commonv1.SecretSource {
return k.SecureSettings
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep more than a valid point thanks for the proposal

Copy link
Contributor

Choose a reason for hiding this comment

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

You need to add // +kubebuilder:object:generate=false before the interface, otherwise controller-gen is not happy:

// HasSecureSettings represents a ConfigPolicySpec that has secure settings.
// +kubebuilder:object:generate=false
type HasSecureSettings interface {
	GetSecureSettings() []commonv1.SecretSource
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>enhancement Enhancement of existing functionality v3.3.0 (next)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants