Skip to content

Commit 58a40ea

Browse files
Merge pull request #1398 from awgreene/operatorgroup-labels
Add OG label to namespaces in OperatorGroup
2 parents 2dea823 + 584b9c3 commit 58a40ea

File tree

2 files changed

+318
-0
lines changed

2 files changed

+318
-0
lines changed

pkg/controller/operators/olm/operatorgroup.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const (
2727
AdminSuffix = "admin"
2828
EditSuffix = "edit"
2929
ViewSuffix = "view"
30+
31+
operatorGroupLabelTemplate = "olm.operatorgroup/%s.%s"
3032
)
3133

3234
var (
@@ -98,6 +100,10 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
98100
logger.WithField("targetNamespaces", targetNamespaces).Debug("updated target namespaces")
99101

100102
if namespacesChanged(targetNamespaces, op.Status.Namespaces) {
103+
// Labels are only applied to namespaces that are not empty strings.
104+
prunedStatusNamespaces := pruneEmptyStrings(op.Status.Namespaces)
105+
prunedTargetNamespaces := pruneEmptyStrings(targetNamespaces)
106+
101107
// Update operatorgroup target namespace selection
102108
logger.WithField("targets", targetNamespaces).Debug("namespace change detected")
103109
op.Status = v1.OperatorGroupStatus{
@@ -109,6 +115,25 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
109115
logger.WithError(err).Warn("operatorgroup update failed")
110116
return err
111117
}
118+
119+
// Remove labels from targetNamespaces that were dropped.
120+
ogLabel := getOperatorGroupLabel(*op)
121+
for _, namespaceName := range setDifference(prunedStatusNamespaces, prunedTargetNamespaces) {
122+
err = a.removeNamespaceLabel(namespaceName, ogLabel)
123+
if err != nil {
124+
logger.WithError(err).Warnf("failed to remove operatorgroup label from namespace %s", namespaceName)
125+
return err
126+
}
127+
}
128+
129+
// Add labels to targetNamespaces that were added.
130+
for _, namespaceName := range setDifference(prunedTargetNamespaces, prunedStatusNamespaces) {
131+
err = a.addNamespaceLabel(namespaceName, ogLabel, "")
132+
if err != nil {
133+
logger.WithError(err).Warnf("failed to add operatorgroup to from namespace %s", namespaceName)
134+
return err
135+
}
136+
}
112137
logger.Debug("namespace change detected and operatorgroup status updated")
113138
// CSV requeue is handled by the succeeding sync in `annotateCSVs`
114139
return nil
@@ -160,6 +185,43 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
160185
return nil
161186
}
162187

188+
// pruneEmptyStrings removes any items from a list that are empty
189+
func pruneEmptyStrings(strings []string) []string {
190+
prunedStrings := []string{}
191+
for _, str := range strings {
192+
if str != "" {
193+
prunedStrings = append(prunedStrings, str)
194+
}
195+
}
196+
return prunedStrings
197+
}
198+
199+
// setDifference implements Set Difference: A - B
200+
// https://en.wikipedia.org/wiki/Complement_(set_theory)#In_programming_languages
201+
func setDifference(a, b []string) (diff []string) {
202+
if len(a) == 0 {
203+
return diff
204+
}
205+
206+
if len(b) == 0 {
207+
return a
208+
}
209+
210+
// Find the Set Difference.
211+
m := make(map[string]bool)
212+
213+
for _, item := range b {
214+
m[item] = true
215+
}
216+
217+
for _, item := range a {
218+
if _, ok := m[item]; !ok {
219+
diff = append(diff, item)
220+
}
221+
}
222+
return diff
223+
}
224+
163225
func (a *Operator) operatorGroupDeleted(obj interface{}) {
164226
op, ok := obj.(*v1.OperatorGroup)
165227
if !ok {
@@ -183,6 +245,53 @@ func (a *Operator) operatorGroupDeleted(obj interface{}) {
183245
logger.WithError(err).Error("failed to delete ClusterRole during garbage collection")
184246
}
185247
}
248+
249+
// Remove OperatorGroup label from targeNamespaces.
250+
ogLabel := getOperatorGroupLabel(*op)
251+
for _, namespaceName := range op.Spec.TargetNamespaces {
252+
a.removeNamespaceLabel(namespaceName, ogLabel)
253+
if err != nil {
254+
logger.WithError(err).Error("failed to remove OperatorGroup Label from Namespace during garbage collection")
255+
}
256+
}
257+
}
258+
259+
func (a *Operator) updateNamespace(namespaceName string, namespaceUpdateFunc func(*corev1.Namespace)) error {
260+
namespace, err := a.opClient.KubernetesInterface().CoreV1().Namespaces().Get(namespaceName, metav1.GetOptions{})
261+
if err != nil {
262+
return err
263+
}
264+
265+
namespaceUpdateFunc(namespace)
266+
267+
_, err = a.opClient.KubernetesInterface().CoreV1().Namespaces().Update(namespace)
268+
if err != nil {
269+
return err
270+
}
271+
return nil
272+
}
273+
274+
func (a *Operator) removeNamespaceLabel(namespaceName, key string) error {
275+
namespaceUpdateFunc := func(namespace *corev1.Namespace) {
276+
delete(namespace.Labels, key)
277+
}
278+
return a.updateNamespace(namespaceName, namespaceUpdateFunc)
279+
}
280+
281+
func (a *Operator) addNamespaceLabel(namespaceName string, key, value string) error {
282+
namespaceUpdateFunc := func(namespace *corev1.Namespace) {
283+
if namespace.Labels == nil {
284+
namespace.Labels = make(map[string]string, 1)
285+
}
286+
namespace.Labels[key] = value
287+
}
288+
return a.updateNamespace(namespaceName, namespaceUpdateFunc)
289+
}
290+
291+
// getOperatorGroupLabel returns a label that is applied to Namespaces to signify that the
292+
// namespace is a part of the OperatorGroup using selectors.
293+
func getOperatorGroupLabel(og v1.OperatorGroup) string {
294+
return fmt.Sprintf(operatorGroupLabelTemplate, og.GetNamespace(), og.GetName())
186295
}
187296

188297
func (a *Operator) annotateCSVs(group *v1.OperatorGroup, targetNamespaces []string, logger *logrus.Entry) error {

test/e2e/operator_groups_e2e_test.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"k8s.io/apimachinery/pkg/api/errors"
1515
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1616
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/labels"
1718
"k8s.io/apimachinery/pkg/util/wait"
1819
"k8s.io/client-go/informers"
1920
"k8s.io/client-go/tools/cache"
@@ -22,6 +23,7 @@ import (
2223
. "github.com/onsi/ginkgo"
2324
v1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1"
2425
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
26+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
2527
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
2628
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
2729
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
@@ -2043,6 +2045,169 @@ var _ = Describe("Operator Group", func() {
20432045
})
20442046
require.NoError(GinkgoT(), err)
20452047
})
2048+
It("OperatorGroupLabels", func() {
2049+
c := newKubeClient(GinkgoT())
2050+
crc := newCRClient(GinkgoT())
2051+
2052+
// Create the namespaces that will have an OperatorGroup Label applied.
2053+
testNamespaceA := genName("namespace-a-")
2054+
testNamespaceB := genName("namespace-b-")
2055+
testNamespaceC := genName("namespace-c-")
2056+
testNamespaces := []string{
2057+
testNamespaceA, testNamespaceB, testNamespaceC,
2058+
}
2059+
2060+
// Create the namespaces
2061+
for _, namespace := range testNamespaces {
2062+
_, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&corev1.Namespace{
2063+
ObjectMeta: metav1.ObjectMeta{
2064+
Name: namespace,
2065+
},
2066+
})
2067+
require.NoError(GinkgoT(), err)
2068+
}
2069+
2070+
// Cleanup namespaces
2071+
defer func() {
2072+
for _, namespace := range testNamespaces {
2073+
err := c.KubernetesInterface().CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{})
2074+
require.NoError(GinkgoT(), err)
2075+
}
2076+
}()
2077+
2078+
// Create an OperatorGroup
2079+
operatorGroup := &v1.OperatorGroup{
2080+
ObjectMeta: metav1.ObjectMeta{
2081+
Name: genName("e2e-operator-group-"),
2082+
Namespace: testNamespaceA,
2083+
},
2084+
Spec: v1.OperatorGroupSpec{
2085+
TargetNamespaces: []string{},
2086+
},
2087+
}
2088+
_, err := crc.OperatorsV1().OperatorGroups(testNamespaceA).Create(operatorGroup)
2089+
require.NoError(GinkgoT(), err)
2090+
2091+
// Cleanup OperatorGroup
2092+
defer func() {
2093+
err := crc.OperatorsV1().OperatorGroups(testNamespaceA).Delete(operatorGroup.GetName(), &metav1.DeleteOptions{})
2094+
require.NoError(GinkgoT(), err)
2095+
}()
2096+
2097+
// Create the OperatorGroup Label
2098+
ogLabel := fmt.Sprintf("olm.operatorgroup/%s.%s", testNamespaceA, operatorGroup.GetName())
2099+
2100+
// Create list options
2101+
listOptions := metav1.ListOptions{
2102+
LabelSelector: labels.Set(map[string]string{ogLabel: ""}).String(),
2103+
}
2104+
2105+
namespaceList, err := pollForListCount(c, listOptions, 0)
2106+
require.NoError(GinkgoT(), err)
2107+
2108+
// Update the OperatorGroup to include a single namespace
2109+
operatorGroup.Spec.TargetNamespaces = []string{testNamespaceA}
2110+
updateOGSpecFunc := updateOperatorGroupSpecFunc(GinkgoT(), crc, testNamespaceA, operatorGroup.GetName())
2111+
require.NoError(GinkgoT(), retry.RetryOnConflict(retry.DefaultBackoff, updateOGSpecFunc(operatorGroup.Spec)))
2112+
2113+
namespaceList, err = pollForListCount(c, listOptions, 1)
2114+
require.NoError(GinkgoT(), err)
2115+
require.True(GinkgoT(), checkForOperatorGroupLabels(operatorGroup, namespaceList.Items))
2116+
2117+
// Update the OperatorGroup to include two namespaces
2118+
operatorGroup.Spec.TargetNamespaces = []string{testNamespaceA, testNamespaceC}
2119+
require.NoError(GinkgoT(), retry.RetryOnConflict(retry.DefaultBackoff, updateOGSpecFunc(operatorGroup.Spec)))
2120+
2121+
namespaceList, err = pollForListCount(c, listOptions, 2)
2122+
require.NoError(GinkgoT(), err)
2123+
require.True(GinkgoT(), checkForOperatorGroupLabels(operatorGroup, namespaceList.Items))
2124+
2125+
// Update the OperatorGroup to include three namespaces
2126+
operatorGroup.Spec.TargetNamespaces = []string{testNamespaceA, testNamespaceB, testNamespaceC}
2127+
require.NoError(GinkgoT(), retry.RetryOnConflict(retry.DefaultBackoff, updateOGSpecFunc(operatorGroup.Spec)))
2128+
2129+
namespaceList, err = pollForListCount(c, listOptions, 3)
2130+
require.NoError(GinkgoT(), err)
2131+
require.True(GinkgoT(), checkForOperatorGroupLabels(operatorGroup, namespaceList.Items))
2132+
2133+
// Update the OperatorGroup to include two namespaces
2134+
operatorGroup.Spec.TargetNamespaces = []string{testNamespaceA, testNamespaceC}
2135+
require.NoError(GinkgoT(), retry.RetryOnConflict(retry.DefaultBackoff, updateOGSpecFunc(operatorGroup.Spec)))
2136+
2137+
namespaceList, err = pollForListCount(c, listOptions, 2)
2138+
require.NoError(GinkgoT(), err)
2139+
require.True(GinkgoT(), checkForOperatorGroupLabels(operatorGroup, namespaceList.Items))
2140+
2141+
// Make the OperatorGroup a Cluster OperatorGroup.
2142+
operatorGroup.Spec.TargetNamespaces = []string{}
2143+
require.NoError(GinkgoT(), retry.RetryOnConflict(retry.DefaultBackoff, updateOGSpecFunc(operatorGroup.Spec)))
2144+
2145+
namespaceList, err = pollForListCount(c, listOptions, 0)
2146+
require.NoError(GinkgoT(), err)
2147+
})
2148+
It("CleanupDeletedOperatorGroupLabels", func() {
2149+
c := newKubeClient(GinkgoT())
2150+
crc := newCRClient(GinkgoT())
2151+
2152+
// Create the namespaces that will have an OperatorGroup Label applied.
2153+
testNamespaceA := genName("namespace-a-")
2154+
testNamespaceB := genName("namespace-b-")
2155+
testNamespaceC := genName("namespace-c-")
2156+
testNamespaces := []string{
2157+
testNamespaceA, testNamespaceB, testNamespaceC,
2158+
}
2159+
2160+
// Create the namespaces
2161+
for _, namespace := range testNamespaces {
2162+
_, err := c.KubernetesInterface().CoreV1().Namespaces().Create(&corev1.Namespace{
2163+
ObjectMeta: metav1.ObjectMeta{
2164+
Name: namespace,
2165+
},
2166+
})
2167+
require.NoError(GinkgoT(), err)
2168+
}
2169+
2170+
// Cleanup namespaces
2171+
defer func() {
2172+
for _, namespace := range testNamespaces {
2173+
err := c.KubernetesInterface().CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{})
2174+
require.NoError(GinkgoT(), err)
2175+
}
2176+
}()
2177+
2178+
// Create an OperatorGroup with three target namespaces.
2179+
operatorGroup := &v1.OperatorGroup{
2180+
ObjectMeta: metav1.ObjectMeta{
2181+
Name: genName("e2e-operator-group-"),
2182+
Namespace: testNamespaceA,
2183+
},
2184+
Spec: v1.OperatorGroupSpec{
2185+
TargetNamespaces: testNamespaces,
2186+
},
2187+
}
2188+
_, err := crc.OperatorsV1().OperatorGroups(testNamespaceA).Create(operatorGroup)
2189+
require.NoError(GinkgoT(), err)
2190+
2191+
// Create the OperatorGroup Label
2192+
ogLabel := fmt.Sprintf("olm.operatorgroup/%s.%s", testNamespaceA, operatorGroup.GetName())
2193+
2194+
// Create list options
2195+
listOptions := metav1.ListOptions{
2196+
LabelSelector: labels.Set(map[string]string{ogLabel: ""}).String(),
2197+
}
2198+
2199+
namespaceList, err := pollForListCount(c, listOptions, 3)
2200+
require.NoError(GinkgoT(), err)
2201+
require.True(GinkgoT(), checkForOperatorGroupLabels(operatorGroup, namespaceList.Items))
2202+
2203+
// Delete the operatorGroup.
2204+
err = crc.OperatorsV1().OperatorGroups(testNamespaceA).Delete(operatorGroup.GetName(), &metav1.DeleteOptions{})
2205+
require.NoError(GinkgoT(), err)
2206+
2207+
// Check that no namespaces have the OperatorGroup.
2208+
namespaceList, err = pollForListCount(c, listOptions, 0)
2209+
require.NoError(GinkgoT(), err)
2210+
})
20462211
})
20472212

20482213
func checkOperatorGroupAnnotations(obj metav1.Object, op *v1.OperatorGroup, checkTargetNamespaces bool, targetNamespaces string) error {
@@ -2115,3 +2280,47 @@ func createProjectAdmin(t GinkgoTInterface, c operatorclient.ClientInterface, na
21152280
_ = c.DeleteRoleBinding(rb.GetNamespace(), rb.GetName(), metav1.NewDeleteOptions(0))
21162281
}
21172282
}
2283+
2284+
func checkForOperatorGroupLabels(operatorGroup *v1.OperatorGroup, namespaces []corev1.Namespace) bool {
2285+
for _, ns := range operatorGroup.Spec.TargetNamespaces {
2286+
if !containsNamespace(namespaces, ns) {
2287+
return false
2288+
}
2289+
}
2290+
return true
2291+
}
2292+
2293+
func updateOperatorGroupSpecFunc(t GinkgoTInterface, crc versioned.Interface, namespace, operatorGroupName string) func(v1.OperatorGroupSpec) func() error {
2294+
return func(operatorGroupSpec v1.OperatorGroupSpec) func() error {
2295+
return func() error {
2296+
fetchedOG, err := crc.OperatorsV1().OperatorGroups(namespace).Get(operatorGroupName, metav1.GetOptions{})
2297+
require.NoError(t, err)
2298+
fetchedOG.Spec = operatorGroupSpec
2299+
_, err = crc.OperatorsV1().OperatorGroups(namespace).Update(fetchedOG)
2300+
return err
2301+
}
2302+
}
2303+
}
2304+
2305+
func pollForListCount(c operatorclient.ClientInterface, listOptions metav1.ListOptions, expectedLength int) (list *corev1.NamespaceList, err error) {
2306+
wait.PollImmediate(pollInterval, pollDuration, func() (bool, error) {
2307+
list, err = c.KubernetesInterface().CoreV1().Namespaces().List(listOptions)
2308+
if err != nil {
2309+
return false, err
2310+
}
2311+
if len(list.Items) == expectedLength {
2312+
return true, nil
2313+
}
2314+
return false, nil
2315+
})
2316+
return
2317+
}
2318+
2319+
func containsNamespace(namespaces []corev1.Namespace, namespaceName string) bool {
2320+
for i := range namespaces {
2321+
if namespaces[i].GetName() == namespaceName {
2322+
return true
2323+
}
2324+
}
2325+
return false
2326+
}

0 commit comments

Comments
 (0)