Skip to content
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/cluster-api-runtime-extensions-nutanix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ A Helm chart for cluster-api-runtime-extensions-nutanix
| imagePullSecrets | list | `[]` | Optional secrets used for pulling the container image |
| namespaceSync.enabled | bool | `true` | |
| namespaceSync.sourceNamespace | string | `""` | |
| namespaceSync.targetNamespaceLabelKey | string | `"caren.nutanix.com/namespace-sync"` | |
| namespaceSync.targetNamespaceLabelSelector | string | `"caren.nutanix.com/namespace-sync"` | |
Copy link
Contributor

Choose a reason for hiding this comment

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

This example needs to change?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's actually the default and it's still fine, it's a label expression which means label exists, matching the existing behaviour.

| nodeSelector | object | `{}` | |
| priorityClassName | string | `"system-cluster-critical"` | Priority class to be used for the pod. |
| resources.limits.cpu | string | `"100m"` | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ spec:
- --defaults-namespace=$(POD_NAMESPACE)
- --namespacesync-enabled={{ .Values.namespaceSync.enabled }}
- --namespacesync-source-namespace={{ default .Release.Namespace .Values.namespaceSync.sourceNamespace }}
- --namespacesync-target-namespace-label-key={{ .Values.namespaceSync.targetNamespaceLabelKey }}
- --namespacesync-target-namespace-label-selector={{ .Values.namespaceSync.targetNamespaceLabelSelector }}
- --enforce-clusterautoscaler-limits-enabled={{ .Values.enforceClusterAutoscalerLimits.enabled }}
- --failure-domain-rollout-enabled={{ .Values.failureDomainRollout.enabled }}
- --failure-domain-rollout-concurrency={{ .Values.failureDomainRollout.concurrency }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@
"sourceNamespace": {
"type": "string"
},
"targetNamespaceLabelKey": {
"targetNamespaceLabelSelector": {
"type": "string"
}
}
Expand Down
2 changes: 1 addition & 1 deletion charts/cluster-api-runtime-extensions-nutanix/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ deployDefaultClusterClasses: true
# target namespace, i.e., every namespace that has a label with a matching key.
namespaceSync:
enabled: true
targetNamespaceLabelKey: caren.nutanix.com/namespace-sync
targetNamespaceLabelSelector: caren.nutanix.com/namespace-sync
# By default, sourceNamespace is the helm release namespace.
sourceNamespace: ""

Expand Down
29 changes: 26 additions & 3 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"

"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -191,10 +192,32 @@ func main() {

if namespacesyncOptions.Enabled {
if namespacesyncOptions.SourceNamespace == "" ||
namespacesyncOptions.TargetNamespaceLabelKey == "" {
namespacesyncOptions.TargetNamespaceLabelSelector == "" {
setupLog.Error(
nil,
"Namespace Sync is enabled, but source namespace and/or target namespace label key are not configured.",
"Namespace Sync is enabled, but source namespace and/or target namespace label selector are not configured.",
)
os.Exit(1)
}

targetSelector, err := metav1.ParseToLabelSelector(namespacesyncOptions.TargetNamespaceLabelSelector)
if err != nil {
setupLog.Error(
err,
"unable to parse target namespace label selector",
"selector",
namespacesyncOptions.TargetNamespaceLabelSelector,
)
os.Exit(1)
}

targetLabelSelector, err := metav1.LabelSelectorAsSelector(targetSelector)
if err != nil {
setupLog.Error(
err,
"unable to convert label selector",
"selector",
namespacesyncOptions.TargetNamespaceLabelSelector,
)
os.Exit(1)
}
Expand All @@ -215,7 +238,7 @@ func main() {
Client: mgr.GetClient(),
UnstructuredCachingClient: unstructuredCachingClient,
SourceClusterClassNamespace: namespacesyncOptions.SourceNamespace,
IsTargetNamespace: namespacesync.NamespaceHasLabelKey(namespacesyncOptions.TargetNamespaceLabelKey),
TargetNamespaceSelector: targetLabelSelector,
}).SetupWithManager(
mgr,
&controller.Options{MaxConcurrentReconciles: namespacesyncOptions.Concurrency},
Expand Down
36 changes: 21 additions & 15 deletions pkg/controllers/namespacesync/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand All @@ -28,16 +29,17 @@ type Reconciler struct {
// SourceClusterClassNamespace is the namespace from which ClusterClasses are copied.
SourceClusterClassNamespace string

// IsTargetNamespace determines whether ClusterClasses should be copied to a given namespace.
IsTargetNamespace func(ns *corev1.Namespace) bool
// TargetNamespaceSelector is a label selector to determine which namespaces should receive
// copies of ClusterClasses and Templates from the source namespace.
TargetNamespaceSelector labels.Selector
}

func (r *Reconciler) SetupWithManager(
mgr ctrl.Manager,
options *controller.Options,
) error {
if r.IsTargetNamespace == nil {
return fmt.Errorf("define IsTargetNamespace function to use controller")
if r.TargetNamespaceSelector == nil {
return fmt.Errorf("TargetNamespaceSelector must be defined to use controller")
}

err := ctrl.NewControllerManagedBy(mgr).
Expand All @@ -51,7 +53,7 @@ func (r *Reconciler) SetupWithManager(
if !ok {
return false
}
return r.IsTargetNamespace(ns)
return r.TargetNamespaceSelector.Matches(labels.Set(ns.GetLabels()))
},
UpdateFunc: func(e event.UpdateEvent) bool {
// Called when an object is already in the cache, and it is either updated,
Expand All @@ -66,7 +68,9 @@ func (r *Reconciler) SetupWithManager(
}
// Only reconcile the namespace if the answer to the question "Is this a
// target namespace?" changed from no to yes.
return !r.IsTargetNamespace(nsOld) && r.IsTargetNamespace(nsNew)
matchesOld := r.TargetNamespaceSelector.Matches(labels.Set(nsOld.GetLabels()))
matchesNew := r.TargetNamespaceSelector.Matches(labels.Set(nsNew.GetLabels()))
return !matchesOld && matchesNew
},
DeleteFunc: func(e event.DeleteEvent) bool {
// Ignore deletes.
Expand All @@ -93,22 +97,23 @@ func (r *Reconciler) SetupWithManager(

func (r *Reconciler) clusterClassToNamespaces(ctx context.Context, o client.Object) []ctrl.Request {
namespaceList := &corev1.NamespaceList{}
err := r.Client.List(ctx, namespaceList)
err := r.Client.List(ctx, namespaceList, &client.ListOptions{
LabelSelector: r.TargetNamespaceSelector,
})
if err != nil {
// TODO Log the error, and record an Event.
return nil
}

rs := []ctrl.Request{}
// Pre-allocate slice with exact capacity since we're using label selector
rs := make([]ctrl.Request, 0, len(namespaceList.Items))
for i := range namespaceList.Items {
ns := &namespaceList.Items[i]
if r.IsTargetNamespace(ns) {
rs = append(rs,
ctrl.Request{
NamespacedName: client.ObjectKeyFromObject(ns),
},
)
}
rs = append(rs,
ctrl.Request{
NamespacedName: client.ObjectKeyFromObject(ns),
},
)
}
return rs
}
Expand All @@ -131,6 +136,7 @@ func (r *Reconciler) Reconcile(
// TODO Consider running in parallel.
for i := range sccs {
scc := &sccs[i]

err := copyClusterClassAndTemplates(
ctx,
r.Client,
Expand Down
6 changes: 0 additions & 6 deletions pkg/controllers/namespacesync/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,6 @@ func copyObjectForCreate[T client.Object](src T, name, namespace string) T {
dst.SetName(name)
dst.SetNamespace(namespace)

// Zero out ManagedFields (clients will set them)
dst.SetManagedFields(nil)
// Zero out OwnerReferences (object is garbage-collected if
// owners are not in the target namespace)
dst.SetOwnerReferences(nil)

// Zero out fields that are ignored by the API server on create
dst.SetCreationTimestamp(metav1.Time{})
dst.SetDeletionGracePeriodSeconds(nil)
Expand Down
28 changes: 19 additions & 9 deletions pkg/controllers/namespacesync/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,48 @@ import (
)

type Options struct {
Enabled bool
Concurrency int
SourceNamespace string
TargetNamespaceLabelKey string
Enabled bool
Concurrency int
SourceNamespace string
TargetNamespaceLabelSelector string
}

func (o *Options) AddFlags(flags *pflag.FlagSet) {
pflag.CommandLine.BoolVar(
flags.BoolVar(
&o.Enabled,
"namespacesync-enabled",
false,
"Enable copying of ClusterClasses and Templates from a source namespace to one or more target namespaces.",
)

pflag.CommandLine.IntVar(
flags.IntVar(
&o.Concurrency,
"namespacesync-concurrency",
10,
"Number of target namespaces to sync concurrently.",
)

pflag.CommandLine.StringVar(
flags.StringVar(
&o.SourceNamespace,
"namespacesync-source-namespace",
"",
"Namespace from which ClusterClasses and Templates are copied.",
)

pflag.CommandLine.StringVar(
&o.TargetNamespaceLabelKey,
flags.StringVar(
&o.TargetNamespaceLabelSelector,
"namespacesync-target-namespace-label-key",
"",
"Label key to determine if a namespace is a target. If a namespace has a label with this key, copy ClusterClasses and Templates to it from the source namespace.", //nolint:lll // Output will be wrapped.
)
_ = flags.MarkDeprecated(
"namespacesync-target-namespace-label-key",
"use namespacesync-target-namespace-label-selector instead",
)
flags.StringVar(
&o.TargetNamespaceLabelSelector,
"namespacesync-target-namespace-label-selector",
"",
"Label selector to determine target namespaces. Namespaces matching this selector will receive copies of ClusterClasses and Templates from the source namespace. Example: 'environment=production' or 'team in (platform,infrastructure)'.", //nolint:lll // Output will be wrapped.
)
}
12 changes: 0 additions & 12 deletions pkg/controllers/namespacesync/label.go

This file was deleted.

61 changes: 0 additions & 61 deletions pkg/controllers/namespacesync/label_test.go

This file was deleted.

13 changes: 12 additions & 1 deletion pkg/controllers/namespacesync/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,22 @@ func TestMain(m *testing.M) {
if err != nil {
panic(fmt.Sprintf("unable to create unstructuredCachineClient: %v", err))
}

// Create a label selector that matches namespaces with the target label key
targetSelector, err := metav1.ParseToLabelSelector(targetNamespaceLabelKey)
if err != nil {
panic(fmt.Sprintf("unable to parse label selector: %v", err))
}
targetLabelSelector, err := metav1.LabelSelectorAsSelector(targetSelector)
if err != nil {
panic(fmt.Sprintf("unable to convert label selector: %v", err))
}

if err := (&Reconciler{
Client: mgr.GetClient(),
UnstructuredCachingClient: unstructuredCachingClient,
SourceClusterClassNamespace: sourceClusterClassNamespace,
IsTargetNamespace: NamespaceHasLabelKey(targetNamespaceLabelKey),
TargetNamespaceSelector: targetLabelSelector,
}).SetupWithManager(mgr, &controller.Options{MaxConcurrentReconciles: 1}); err != nil {
panic(fmt.Sprintf("unable to create reconciler: %v", err))
}
Expand Down
Loading