diff --git a/VERSION b/VERSION index f7fbf5e..4ea5caf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.7.0-dev \ No newline at end of file +v0.8.0 \ No newline at end of file diff --git a/pkg/clusters/cluster.go b/pkg/clusters/cluster.go index 4ba075d..67e7991 100644 --- a/pkg/clusters/cluster.go +++ b/pkg/clusters/cluster.go @@ -36,6 +36,16 @@ func New(id string) *Cluster { return c } +// NewTestClusterFromClient creates a Cluster from a given client. +// Note that this method is meant for testing purposes only and it does not result in a fully functional Cluster. +// Calling anything except Client() on the resulting Cluster is undefined and might lead to panics or unexpected behavior. +func NewTestClusterFromClient(id string, cli client.Client) *Cluster { + c := &Cluster{} + c.InitializeID(id) + c.client = cli + return c +} + // WithConfigPath sets the config path for the cluster. // Returns the cluster for chaining. func (c *Cluster) WithConfigPath(cfgPath string) *Cluster { @@ -51,6 +61,7 @@ func (c *Cluster) WithRESTConfig(cfg *rest.Config) *Cluster { } // RegisterConfigPathFlag adds a flag '---cluster' for the cluster's config path to the given flag set. +// If only a single kubeconfig is required, RegisterSingleConfigPathFlag can be used instead to have the flag named '--kubeconfig'. // Panics if the cluster's id is not set. func (c *Cluster) RegisterConfigPathFlag(flags *flag.FlagSet) { if !c.HasID() { @@ -59,6 +70,12 @@ func (c *Cluster) RegisterConfigPathFlag(flags *flag.FlagSet) { flags.StringVar(&c.cfgPath, fmt.Sprintf("%s-cluster", c.id), "", fmt.Sprintf("Path to the %s cluster kubeconfig file or directory containing either a kubeconfig or host, token, and ca file. Leave empty to use in-cluster config.", c.id)) } +// RegisterSingleConfigPathFlag adds a '--kubeconfig' flag for the cluster's config path to the given flag set. +// If more than one kubeconfig is required, consider using RegisterConfigPathFlag instead, which is identical, but names the flag '---cluster'. +func (c *Cluster) RegisterSingleConfigPathFlag(flags *flag.FlagSet) { + flags.StringVar(&c.cfgPath, "kubeconfig", "", "Path to the kubeconfig file or directory containing either a kubeconfig or host, token, and ca file. Leave empty to use in-cluster config.") +} + // WithClientOptions allows to overwrite the default client options. // It must be called before InitializeClient(). // Note that using this method disables the the scheme injection during client initialization. diff --git a/pkg/controller/predicates.go b/pkg/controller/predicates.go index bbacfd1..3bb0f2f 100644 --- a/pkg/controller/predicates.go +++ b/pkg/controller/predicates.go @@ -5,6 +5,7 @@ package controller import ( "reflect" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -179,6 +180,22 @@ func LostLabelPredicate(key, val string) predicate.Predicate { } } +// LabelSelectorPredicate returns a predicate based on a label selector. +// Opposed to the similarly named function from the controller-runtime library, this one works on label.Selector +// instead of metav1.LabelSelector. +func LabelSelectorPredicate(sel labels.Selector) predicate.Predicate { + return predicate.NewPredicateFuncs(func(obj client.Object) bool { + if obj == nil { + return false + } + ls := obj.GetLabels() + if ls == nil { + ls = map[string]string{} + } + return sel.Matches(labels.Set(ls)) + }) +} + ///////////////////////// /// STATUS PREDICATES /// ///////////////////////// diff --git a/pkg/controller/predicates_test.go b/pkg/controller/predicates_test.go index b7ddca3..010f93e 100644 --- a/pkg/controller/predicates_test.go +++ b/pkg/controller/predicates_test.go @@ -6,6 +6,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" @@ -114,8 +115,24 @@ var _ = Describe("Predicates", func() { It("should detect changes to the labels", func() { pHasFoo := ctrlutils.HasLabelPredicate("foo", "") pHasBar := ctrlutils.HasLabelPredicate("bar", "") + matchesEverything := ctrlutils.LabelSelectorPredicate(labels.Everything()) + matchesNothing := ctrlutils.LabelSelectorPredicate(labels.Nothing()) pHasFooWithFoo := ctrlutils.HasLabelPredicate("foo", "foo") pHasFooWithBar := ctrlutils.HasLabelPredicate("foo", "bar") + fooWithFooSelector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "foo", + }, + }) + Expect(err).ToNot(HaveOccurred()) + fooWithBarSelector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + "foo": "bar", + }, + }) + Expect(err).ToNot(HaveOccurred()) + pHasFooWithFooViaSelector := ctrlutils.LabelSelectorPredicate(fooWithFooSelector) + pHasFooWithBarViaSelector := ctrlutils.LabelSelectorPredicate(fooWithBarSelector) pGotFoo := ctrlutils.GotLabelPredicate("foo", "") pGotFooWithFoo := ctrlutils.GotLabelPredicate("foo", "foo") pGotFooWithBar := ctrlutils.GotLabelPredicate("foo", "bar") @@ -124,9 +141,13 @@ var _ = Describe("Predicates", func() { pLostFooWithBar := ctrlutils.LostLabelPredicate("foo", "bar") By("old and new resource are equal") e := updateEvent(base, changed) + Expect(matchesEverything.Update(e)).To(BeTrue(), "'everything' LabelSelector should always match") + Expect(matchesNothing.Update(e)).To(BeFalse(), "'nothing' LabelSelector should never match") Expect(pHasFoo.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") Expect(pHasFooWithFoo.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") + Expect(pHasFooWithFooViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return false if the labels are not matched") Expect(pHasFooWithBar.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") + Expect(pHasFooWithBarViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return falseif the labels are not matched") Expect(pGotFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels") Expect(pGotFooWithFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels") Expect(pGotFooWithBar.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels") @@ -138,10 +159,14 @@ var _ = Describe("Predicates", func() { "foo": "foo", }) e = updateEvent(base, changed) + Expect(matchesEverything.Update(e)).To(BeTrue(), "'everything' LabelSelector should always match") + Expect(matchesNothing.Update(e)).To(BeFalse(), "'nothing' LabelSelector should never match") Expect(pHasFoo.Update(e)).To(BeTrue(), "HasLabelPredicate should return true if the label is there") Expect(pHasBar.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if the label is not there") Expect(pHasFooWithFoo.Update(e)).To(BeTrue(), "HasLabelPredicate should return true if the label is there and has the fitting value") + Expect(pHasFooWithFooViaSelector.Update(e)).To(BeTrue(), "LabelSelectorPredicate should return true if the labels are matched") Expect(pHasFooWithBar.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if the label is there but has the wrong value") + Expect(pHasFooWithBarViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return false if the labels are not matched") Expect(pGotFoo.Update(e)).To(BeTrue(), "GotLabelPredicate should return true if the label was added") Expect(pGotFooWithFoo.Update(e)).To(BeTrue(), "GotLabelPredicate should return true if the label was added with the correct value") Expect(pGotFooWithBar.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if the label was added but with the wrong value") @@ -154,10 +179,14 @@ var _ = Describe("Predicates", func() { "foo": "bar", }) e = updateEvent(base, changed) + Expect(matchesEverything.Update(e)).To(BeTrue(), "'everything' LabelSelector should always match") + Expect(matchesNothing.Update(e)).To(BeFalse(), "'nothing' LabelSelector should never match") Expect(pHasFoo.Update(e)).To(BeTrue(), "HasLabelPredicate should return true if the label is there") Expect(pHasBar.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if the label is not there") Expect(pHasFooWithFoo.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if the label is there but has the wrong value") + Expect(pHasFooWithFooViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return false if the labels are not matched") Expect(pHasFooWithBar.Update(e)).To(BeTrue(), "HasLabelPredicate should return true if the label is there and has the fitting value") + Expect(pHasFooWithBarViaSelector.Update(e)).To(BeTrue(), "LabelSelectorPredicate should return true if the labels are matched") Expect(pGotFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if the label was there before") Expect(pGotFooWithFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if the label was changed but to the wrong value") Expect(pGotFooWithBar.Update(e)).To(BeTrue(), "GotLabelPredicate should return true if the label was changed to the correct value") @@ -168,9 +197,13 @@ var _ = Describe("Predicates", func() { base = changed.DeepCopy() changed.SetLabels(nil) e = updateEvent(base, changed) + Expect(matchesEverything.Update(e)).To(BeTrue(), "'everything' LabelSelector should always match") + Expect(matchesNothing.Update(e)).To(BeFalse(), "'nothing' LabelSelector should never match") Expect(pHasFoo.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") Expect(pHasFooWithFoo.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") + Expect(pHasFooWithFooViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return false if the labels are not matched") Expect(pHasFooWithBar.Update(e)).To(BeFalse(), "HasLabelPredicate should return false if there are no labels") + Expect(pHasFooWithBarViaSelector.Update(e)).To(BeFalse(), "LabelSelectorPredicate should return false if the labels are not matched") Expect(pGotFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels") Expect(pGotFooWithFoo.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels") Expect(pGotFooWithBar.Update(e)).To(BeFalse(), "GotLabelPredicate should return false if there are no labels")