redhat-cop/namespace-configuration-operator

Add support for template-based label/annotation matching

Opened this issue · 4 comments

Use case

When creating namespace configurations, there are scenarios where we want to match labels/annotations based on dynamic values derived from the namespace itself.

For example, a common pattern is to have ArgoCD instances managing namespaces where the instance name is derived from the namespace name:

metadata:
  name: my-project
  labels:
    argocd.argoproj.io/managed-by: my-project-argo

Currently, we can't create a NamespaceConfig that matches based on this pattern without requiring additional trigger labels.

Proposed solution

Add a new field to the NamespaceConfig spec for template-based matching:

apiVersion: redhatcop.redhat.io/v1alpha1
kind: NamespaceConfig
metadata:
  name: gitops-config
spec:
  labelMatchTemplate:
    argocd.argoproj.io/managed-by: "{{ .Name }}-argo"
  templates:
    - objectTemplate: |
        apiVersion: v1
        kind: Namespace
        metadata:
          name: {{ .Name }}-argo

This would allow the operator to:

  1. Evaluate the template expressions against the namespace
  2. Check if the resulting key-value pairs match the namespace's actual labels/annotations

Benefits

  • More intuitive configurations that can use self-referential patterns
  • Reduction in redundant labeling (no need for separate trigger labels)
  • Better support for common patterns like ArgoCD management where instance names follow a convention
  • More maintainable configurations as relationships are more explicit

Alternatives considered

  1. Using standard label selectors with Exists operator - less precise, can't validate naming convention
  2. Using separate trigger labels - works but adds unnecessary complexity
  3. Using regexp matching - possible but less intuitive and harder to maintain

Would this feature be valuable to the project? Happy to provide more details or discuss alternative approaches.

your alternative #1 would be my recommendation for this use case. Can you explain in which sense it is less precise?

If I just check that a label with the key argocd.argoproj.io/managed-by exists, then the NamespaceConfig may unintentionally be applied to non-tenant namespaces. So then we need to have some additional selector, e.g. myplatform.com/tenant-gitops-namespace: tenant-app-argo, but then a team has to have both selectors on their namespace, and they need to match each other (and argocd.argoproj.io/managed-by will refer to a namespace that does not yet exist).

I am not sure I fully understand the use case. But it looks like you need to be able to do two things:

  1. discriminate between tenant and system namespaces
  2. for tenant namespaces, apply a namespace config.

It also seems like you are trying to discriminate between tenant and system namespace with a label argocd.argoproj.io/managed-by that does not really contain that information as you use it for both tenant and non-tenant namespaces. Assuming you have a good reason for doping that (as opposed to simply add another label: tenant: "true", for example), then you'll need to have either whitelist (of tenant namespaces) or a blacklist (of system namespaces). With the list information you can add a second selector that actually matches only the namespaces you want.
Finally I don't understand your initial example: you cannot create namespaces in a namespace config. You are supposed to create manifests within the matched (and pre-existing) namespaces.

The use case is a platform-as-a-service where each tenant gets their own Argo CD server. The Argo CD server is in its own namespace so that it doesn't use the same resource quota as the resources in the namespace itself, and also because teams may want to be able to configure separate access control for deployment and development. So it's not that we're using the namespace configuration operator to create the namespace, it's that we're using it to create an additional, companion namespace.

For realising this with selectors I tried these options:

  1. Have myplatform.com/tenant-gitops: "true" as the required label. This would automatically generate an additional namespace named <namespace-name>-argo. But you'd also have to remember to put argocd.argoproj.io/managed-by: <namespace-name>-argo on the namespace or I assume it wouldn't work correctly in difficult-to-reason-about ways.

  2. Have argocd.argoproj.io/managed-by as the required label key, and use its value to generate the name of the Argo CD namespace—so then any Argo CD-managed namespace gets configured in this way. But using a selector which is a label that is defined by a different operator this might inadvertently target namespaces which aren't tenant namespaces.

  3. Require both of the above. So you need to have myplatform.com/tenant-gitops: "true" and argocd.argoproj.io/managed-by on the namespace to get the NamespaceConfig; i.e.

    spec:
      labelSelector:
        matchExpressions:
          - key: argocd.argoproj.io/managed-by
            operator: Exists
      matchLabels:
        myplatform.com/tenant-gitops: "true"

This proposal is essentially to create an option 4, to be able to do this with a single label.

I went with option 3 for now, but the NamespaceConfig resource in Argo CD had an error on it like, "the object has been modified; please apply your changes to the latest version and try again" (other NamespaceConfigs were fine), and when trying to force sync the resource, it actually deleted all the resources in the openshift-gitops namespace and the openshift-gitops namespace itself got stuck in a Terminating state. So I think deleting this NamespaceConfig somehow caused openshift-gitops to delete itself 🧐