Skip to content

Commit

Permalink
Merge pull request #37 from JoshVanL/bundle-namespace-selector
Browse files Browse the repository at this point in the history
Bundle: `namespaceSelector`
  • Loading branch information
jetstack-bot authored Jul 21, 2022
2 parents 1d71e86 + 9a99ed8 commit 16d26e2
Show file tree
Hide file tree
Showing 14 changed files with 595 additions and 110 deletions.
2 changes: 1 addition & 1 deletion deploy/charts/trust/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ rules:
- ""
resources:
- "configmaps"
verbs: ["get", "list", "create", "update", "watch"]
verbs: ["get", "list", "create", "update", "watch", "delete"]
- apiGroups:
- ""
resources:
Expand Down
24 changes: 21 additions & 3 deletions deploy/charts/trust/templates/trust.cert-manager.io_bundles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.6.1
controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: bundles.trust.cert-manager.io
spec:
Expand Down Expand Up @@ -94,14 +94,23 @@ spec:
type: object
properties:
configMap:
description: ConfigMap is the target ConfigMap in all Namespaces that all Bundle source data will be synced to.
description: ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
namespaceSelector:
description: NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
type: object
properties:
matchLabels:
description: MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
type: object
additionalProperties:
type: string
status:
description: Status of the Bundle. This is set and managed automatically.
type: object
Expand Down Expand Up @@ -141,14 +150,23 @@ spec:
type: object
properties:
configMap:
description: ConfigMap is the target ConfigMap in all Namespaces that all Bundle source data will be synced to.
description: ConfigMap is the target ConfigMap in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
namespaceSelector:
description: NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector.
type: object
properties:
matchLabels:
description: MatchLabels matches on the set of labels that must be present on a Namespace for the Bundle target to be synced there.
type: object
additionalProperties:
type: string
served: true
storage: true
subresources:
Expand Down
15 changes: 14 additions & 1 deletion pkg/apis/trust/v1alpha1/types_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,22 @@ type BundleSource struct {
// BundleTarget is the target resource that the Bundle will sync all source
// data to.
type BundleTarget struct {
// ConfigMap is the target ConfigMap in all Namespaces that all Bundle source
// ConfigMap is the target ConfigMap in Namespaces that all Bundle source
// data will be synced to.
ConfigMap *KeySelector `json:"configMap,omitempty"`

// NamespaceSelector will, if set, only sync the target resource in
// Namespaces which match the selector.
// +optional
NamespaceSelector *NamespaceSelector `json:"namespaceSelector,omitempty"`
}

// NamespaceSelector defines selectors to match on Namespaces.
type NamespaceSelector struct {
// MatchLabels matches on the set of labels that must be present on a
// Namespace for the Bundle target to be synced there.
// +optional
MatchLabels map[string]string `json:"matchLabels,omitempty"`
}

// SourceObjectKeySelector is a reference to a source object and its `data` key
Expand Down
29 changes: 29 additions & 0 deletions pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 19 additions & 3 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/record"
"k8s.io/utils/clock"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -85,6 +86,15 @@ func (b *bundle) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result,
return ctrl.Result{}, fmt.Errorf("failed to get %q: %s", req.NamespacedName, err)
}

namespaceSelector := labels.Everything()
if nsSelector := bundle.Spec.Target.NamespaceSelector; nsSelector != nil && nsSelector.MatchLabels != nil {
namespaceSelector, err = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: nsSelector.MatchLabels})
if err != nil {
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "NamespaceSelectorError", "Failed to build namespace match labels selector: %s", err)
return ctrl.Result{}, fmt.Errorf("failed to build NamespaceSelector: %w", err)
}
}

var namespaceList corev1.NamespaceList
if err := b.lister.List(ctx, &namespaceList); err != nil {
log.Error(err, "failed to list namespaces")
Expand Down Expand Up @@ -166,7 +176,7 @@ func (b *bundle) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result,
continue
}

synced, err := b.syncTarget(ctx, log, &bundle, namespace.Name, data)
synced, err := b.syncTarget(ctx, log, &bundle, namespaceSelector, &namespace, data)
if err != nil {
log.Error(err, "failed sync bundle to target namespace")
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "SyncTargetFailed", "Failed to sync target in Namespace %q: %s", namespace.Name, err)
Expand All @@ -192,11 +202,17 @@ func (b *bundle) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result,
needsUpdate = true
}

message := "Successfully synced Bundle to all namespaces"
if nsSelector := bundle.Spec.Target.NamespaceSelector; nsSelector != nil && nsSelector.MatchLabels != nil {
message = fmt.Sprintf("Successfully synced Bundle to namespaces with selector [matchLabels:%v]",
nsSelector.MatchLabels)
}

syncedCondition := trustapi.BundleCondition{
Type: trustapi.BundleConditionSynced,
Status: corev1.ConditionTrue,
Reason: "Synced",
Message: "Successfully synced Bundle to all namespaces",
Message: message,
}

if !needsUpdate && bundleHasCondition(&bundle, syncedCondition) {
Expand All @@ -206,6 +222,6 @@ func (b *bundle) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result,
log.V(2).Info("successfully synced bundle")

b.setBundleCondition(&bundle, syncedCondition)
b.recorder.Eventf(&bundle, corev1.EventTypeNormal, "Synced", "Successfully synced Bundle to all namespaces")
b.recorder.Eventf(&bundle, corev1.EventTypeNormal, "Synced", message)
return ctrl.Result{}, b.client.Status().Update(ctx, &bundle)
}
133 changes: 116 additions & 17 deletions pkg/bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -302,18 +303,93 @@ func Test_Reconcile(t *testing.T) {
gen.SetBundleResourceVersion("1001"),
gen.SetBundleStatus(trustapi.BundleStatus{
Target: &trustapi.BundleTarget{ConfigMap: &trustapi.KeySelector{Key: targetKey}},
Conditions: []trustapi.BundleCondition{
{
Type: trustapi.BundleConditionSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: fixedmetatime,
Reason: "Synced",
Message: "Successfully synced Bundle to all namespaces",
ObservedGeneration: bundleGeneration,
Conditions: []trustapi.BundleCondition{{
Type: trustapi.BundleConditionSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: fixedmetatime,
Reason: "Synced",
Message: "Successfully synced Bundle to all namespaces",
ObservedGeneration: bundleGeneration,
}},
}),
),
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: trustNamespace, Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: "ns-1", Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: "ns-2", Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
),
expEvent: "Normal Synced Successfully synced Bundle to all namespaces",
},
"if Bundle not synced everywhere, sync except Namespaces that don't match labels and update Synced": {
existingObjects: append(namespaces, sourceConfigMap, sourceSecret, gen.BundleFrom(baseBundle,
gen.SetBundleTargetNamespaceSelectorMatchLabels(map[string]string{"foo": "bar"}),
),
&corev1.Namespace{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{
Name: "random-namespace",
Labels: map[string]string{"foo": "bar"},
},
},
&corev1.Namespace{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{
Name: "another-random-namespace",
Labels: map[string]string{"foo": "bar"},
},
},
),
expResult: ctrl.Result{},
expError: false,
expObjects: append(namespaces, sourceConfigMap, sourceSecret,
gen.BundleFrom(baseBundle,
gen.SetBundleResourceVersion("1001"),
gen.SetBundleTargetNamespaceSelectorMatchLabels(map[string]string{"foo": "bar"}),
gen.SetBundleStatus(trustapi.BundleStatus{
Target: &trustapi.BundleTarget{
ConfigMap: &trustapi.KeySelector{Key: targetKey},
NamespaceSelector: &trustapi.NamespaceSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
},
Conditions: []trustapi.BundleCondition{{
Type: trustapi.BundleConditionSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: &metav1.Time{Time: fixedclock.Now().Local()},
Reason: "Synced",
Message: "Successfully synced Bundle to namespaces with selector [matchLabels:map[foo:bar]]",
ObservedGeneration: bundleGeneration,
}},
}),
),
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: "random-namespace", Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: "another-random-namespace", Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
),
expEvent: "Normal Synced Successfully synced Bundle to namespaces with selector [matchLabels:map[foo:bar]]",
},
"if Bundle not synced everywhere, sync except Namespaces that don't match labels and update Synced. Should delete ConfigMaps in wrong namespaces.": {
existingObjects: append(namespaces, sourceConfigMap, sourceSecret, gen.BundleFrom(baseBundle,
gen.SetBundleTargetNamespaceSelectorMatchLabels(map[string]string{"foo": "bar"}),
),
&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Namespace: trustNamespace, Name: baseBundle.Name, OwnerReferences: baseBundleOwnerRef, ResourceVersion: "1"},
Expand All @@ -330,13 +406,39 @@ func Test_Reconcile(t *testing.T) {
Data: map[string]string{targetKey: "A\nB\nC\n"},
},
),
expEvent: "Normal Synced Successfully synced Bundle to all namespaces",
expResult: ctrl.Result{},
expError: false,
expObjects: append(namespaces, sourceConfigMap, sourceSecret,
gen.BundleFrom(baseBundle,
gen.SetBundleResourceVersion("1001"),
gen.SetBundleTargetNamespaceSelectorMatchLabels(map[string]string{"foo": "bar"}),
gen.SetBundleStatus(trustapi.BundleStatus{
Target: &trustapi.BundleTarget{
ConfigMap: &trustapi.KeySelector{Key: targetKey},
NamespaceSelector: &trustapi.NamespaceSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
},
Conditions: []trustapi.BundleCondition{{
Type: trustapi.BundleConditionSynced,
Status: corev1.ConditionTrue,
LastTransitionTime: &metav1.Time{Time: fixedclock.Now().Local()},
Reason: "Synced",
Message: "Successfully synced Bundle to namespaces with selector [matchLabels:map[foo:bar]]",
ObservedGeneration: bundleGeneration,
}},
}),
),
),
expEvent: "Normal Synced Successfully synced Bundle to namespaces with selector [matchLabels:map[foo:bar]]",
},
"if Bundle synced but doesn't have owner reference, should sync and update": {
existingObjects: append(namespaces, sourceConfigMap, sourceSecret,
gen.BundleFrom(baseBundle,
gen.SetBundleStatus(trustapi.BundleStatus{
Target: &trustapi.BundleTarget{ConfigMap: &trustapi.KeySelector{Key: targetKey}},
Target: &trustapi.BundleTarget{
ConfigMap: &trustapi.KeySelector{Key: targetKey},
},
Conditions: []trustapi.BundleCondition{
{
Type: trustapi.BundleConditionSynced,
Expand Down Expand Up @@ -557,9 +659,7 @@ func Test_Reconcile(t *testing.T) {
case event = <-fakerecorder.Events:
default:
}
if event != test.expEvent {
t.Errorf("unexpected event, exp=%q got=%q", test.expEvent, event)
}
assert.Equal(t, test.expEvent, event)

for _, expectedObject := range test.expObjects {
expObj := expectedObject.(client.Object)
Expand All @@ -578,10 +678,9 @@ func Test_Reconcile(t *testing.T) {
}

err := fakeclient.Get(context.TODO(), client.ObjectKeyFromObject(expObj), actual)
if err != nil {
t.Errorf("unexpected error getting expected object: %s", err)
} else if !apiequality.Semantic.DeepEqual(expObj, actual) {
t.Errorf("unexpected expected object, exp=%#+v got=%#+v", expObj, actual)
assert.NoError(t, err)
if !apiequality.Semantic.DeepEqual(expObj, actual) {
t.Errorf("unexpected expected object\nexp=%#+v\ngot=%#+v", expObj, actual)
}
}
})
Expand Down
Loading

0 comments on commit 16d26e2

Please sign in to comment.