Skip to content

Commit b9638dc

Browse files
ankitathomasdtfranz
authored andcommitted
Adds the OperatorDeprecated Condition to the status of Subscriptions when the bundle has been deprecated.
Co-authored-by: Ankita Thomas <[email protected]> Co-authored-by: Bryce Palmer <[email protected]> Signed-off-by: Daniel Franz <[email protected]>
1 parent c6620c2 commit b9638dc

File tree

15 files changed

+440
-34
lines changed

15 files changed

+440
-34
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ require (
2424
github.com/onsi/gomega v1.27.10
2525
github.com/openshift/api v3.9.0+incompatible
2626
github.com/openshift/client-go v0.0.0-20220525160904-9e1acff93e4a
27-
github.com/operator-framework/api v0.19.0
27+
github.com/operator-framework/api v0.20.0
2828
github.com/operator-framework/operator-registry v1.33.0
2929
github.com/otiai10/copy v1.12.0
3030
github.com/pkg/errors v0.9.1

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,8 @@ github.com/openshift/api v0.0.0-20221021112143-4226c2167e40 h1:PxjGCA72RtsdHWToZ
621621
github.com/openshift/api v0.0.0-20221021112143-4226c2167e40/go.mod h1:aQ6LDasvHMvHZXqLHnX2GRmnfTWCF/iIwz8EMTTIE9A=
622622
github.com/openshift/client-go v0.0.0-20221019143426-16aed247da5c h1:CV76yFOTXmq9VciBR3Bve5ZWzSxdft7gaMVB3kS0rwg=
623623
github.com/openshift/client-go v0.0.0-20221019143426-16aed247da5c/go.mod h1:lFMO8mLHXWFzSdYvGNo8ivF9SfF6zInA8ZGw4phRnUE=
624-
github.com/operator-framework/api v0.19.0 h1:QU1CTJU+CufoeneA5rsNlP/uP96s8vDHWUYDFZTauzA=
625-
github.com/operator-framework/api v0.19.0/go.mod h1:SCCslqke6AVOJ5JM+NqNE1CHuAgJLScsL66pnPaSMXs=
624+
github.com/operator-framework/api v0.20.0 h1:A2YCRhr+6s0k3pRJacnwjh1Ue8BqjIGuQ2jvPg9XCB4=
625+
github.com/operator-framework/api v0.20.0/go.mod h1:rXPOhrQ6mMeXqCmpDgt1ALoar9ZlHL+Iy5qut9R99a4=
626626
github.com/operator-framework/operator-registry v1.33.0 h1:rFvYf6vLdXSUhoePhg8w1S5FHgyFraiNLXDwl47epck=
627627
github.com/operator-framework/operator-registry v1.33.0/go.mod h1:1V/m2m7iH/o5ROEuMxWa/yhj4ExaPGT6V/ncGS+m6Js=
628628
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=

pkg/controller/operators/catalog/operator.go

+1
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
346346
subscription.WithAppendedReconcilers(subscription.ReconcilerFromLegacySyncHandler(op.syncSubscriptions, nil)),
347347
subscription.WithRegistryReconcilerFactory(op.reconciler),
348348
subscription.WithGlobalCatalogNamespace(op.namespace),
349+
subscription.WithSourceProvider(resolverSourceProvider),
349350
)
350351
if err != nil {
351352
return nil, err

pkg/controller/operators/catalog/subscription/config.go

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
1212
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
13+
resolverCache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
1314
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
1415
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
1516
)
@@ -26,6 +27,7 @@ type syncerConfig struct {
2627
reconcilers kubestate.ReconcilerChain
2728
registryReconcilerFactory reconciler.RegistryReconcilerFactory
2829
globalCatalogNamespace string
30+
sourceProvider resolverCache.SourceProvider
2931
}
3032

3133
// SyncerOption is a configuration option for a subscription syncer.
@@ -128,6 +130,12 @@ func WithGlobalCatalogNamespace(namespace string) SyncerOption {
128130
}
129131
}
130132

133+
func WithSourceProvider(provider resolverCache.SourceProvider) SyncerOption {
134+
return func(config *syncerConfig) {
135+
config.sourceProvider = provider
136+
}
137+
}
138+
131139
func newInvalidConfigError(msg string) error {
132140
return errors.Errorf("invalid subscription syncer config: %s", msg)
133141
}

pkg/controller/operators/catalog/subscription/reconciler.go

+127-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"errors"
66
"fmt"
77
"sort"
8+
"strings"
89

910
"github.com/sirupsen/logrus"
11+
corev1 "k8s.io/api/core/v1"
1012
apierrors "k8s.io/apimachinery/pkg/api/errors"
1113
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1214
"k8s.io/apimachinery/pkg/labels"
@@ -18,8 +20,10 @@ import (
1820
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
1921
listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
2022
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
23+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
2124
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
2225
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
26+
"github.com/operator-framework/operator-registry/pkg/api"
2327
)
2428

2529
// ReconcilerFromLegacySyncHandler returns a reconciler that invokes the given legacy sync handler and on delete funcs.
@@ -57,6 +61,7 @@ type catalogHealthReconciler struct {
5761
catalogLister listers.CatalogSourceLister
5862
registryReconcilerFactory reconciler.RegistryReconcilerFactory
5963
globalCatalogNamespace string
64+
sourceProvider cache.SourceProvider
6065
}
6166

6267
// Reconcile reconciles subscription catalog health conditions.
@@ -84,7 +89,15 @@ func (c *catalogHealthReconciler) Reconcile(ctx context.Context, in kubestate.St
8489
break
8590
}
8691

87-
next, err = s.UpdateHealth(c.now(), c.client.OperatorsV1alpha1().Subscriptions(ns), catalogHealth...)
92+
var healthUpdated, deprecationUpdated bool
93+
next, healthUpdated = s.UpdateHealth(c.now(), catalogHealth...)
94+
deprecationUpdated, err = c.updateDeprecatedStatus(ctx, s.Subscription())
95+
if err != nil {
96+
return next, err
97+
}
98+
if healthUpdated || deprecationUpdated {
99+
_, err = c.client.OperatorsV1alpha1().Subscriptions(ns).UpdateStatus(ctx, s.Subscription(), metav1.UpdateOptions{})
100+
}
88101
case SubscriptionExistsState:
89102
if s == nil {
90103
err = errors.New("nil state")
@@ -109,6 +122,119 @@ func (c *catalogHealthReconciler) Reconcile(ctx context.Context, in kubestate.St
109122
return
110123
}
111124

125+
// updateDeprecatedStatus adds deprecation status conditions to the subscription when present in the cache entry then
126+
// returns a bool value of true if any changes to the existing subscription have occurred.
127+
func (c *catalogHealthReconciler) updateDeprecatedStatus(ctx context.Context, sub *v1alpha1.Subscription) (bool, error) {
128+
if c.sourceProvider == nil {
129+
return false, nil
130+
}
131+
132+
source, ok := c.sourceProvider.Sources(sub.Namespace)[cache.SourceKey{
133+
Name: sub.Spec.CatalogSource,
134+
Namespace: sub.Namespace,
135+
}]
136+
if !ok {
137+
return false, nil
138+
}
139+
snapshot, err := source.Snapshot(ctx)
140+
if err != nil {
141+
return false, err
142+
}
143+
if len(snapshot.Entries) == 0 {
144+
return false, nil
145+
}
146+
147+
changed := false
148+
rollupMessages := []string{}
149+
var deprecations *cache.Deprecations
150+
151+
found := false
152+
for _, entry := range snapshot.Entries {
153+
// Find the cache entry that matches this subscription
154+
if entry.SourceInfo == nil || entry.Package() != sub.Spec.Package {
155+
continue
156+
}
157+
if sub.Spec.Channel != "" && entry.Channel() != sub.Spec.Channel {
158+
continue
159+
}
160+
if sub.Status.InstalledCSV != entry.Name {
161+
continue
162+
}
163+
deprecations = entry.SourceInfo.Deprecations
164+
found = true
165+
break
166+
}
167+
if !found {
168+
// No matching entry found
169+
return false, nil
170+
}
171+
conditionTypes := []v1alpha1.SubscriptionConditionType{
172+
v1alpha1.SubscriptionPackageDeprecated,
173+
v1alpha1.SubscriptionChannelDeprecated,
174+
v1alpha1.SubscriptionBundleDeprecated,
175+
}
176+
for _, conditionType := range conditionTypes {
177+
oldCondition := sub.Status.GetCondition(conditionType)
178+
var deprecation *api.Deprecation
179+
if deprecations != nil {
180+
switch conditionType {
181+
case v1alpha1.SubscriptionPackageDeprecated:
182+
deprecation = deprecations.Package
183+
case v1alpha1.SubscriptionChannelDeprecated:
184+
deprecation = deprecations.Channel
185+
case v1alpha1.SubscriptionBundleDeprecated:
186+
deprecation = deprecations.Bundle
187+
}
188+
}
189+
if deprecation != nil {
190+
if conditionType == v1alpha1.SubscriptionChannelDeprecated && sub.Spec.Channel == "" {
191+
// Special case: If optional field sub.Spec.Channel is unset do not apply a channel
192+
// deprecation message and remove them if any exist.
193+
sub.Status.RemoveConditions(conditionType)
194+
if oldCondition.Status == corev1.ConditionTrue {
195+
changed = true
196+
}
197+
continue
198+
}
199+
newCondition := v1alpha1.SubscriptionCondition{
200+
Type: conditionType,
201+
Message: deprecation.Message,
202+
Status: corev1.ConditionTrue,
203+
LastTransitionTime: c.now(),
204+
}
205+
rollupMessages = append(rollupMessages, deprecation.Message)
206+
if oldCondition.Message != newCondition.Message {
207+
// oldCondition's message was empty or has changed; add or update the condition
208+
sub.Status.SetCondition(newCondition)
209+
changed = true
210+
}
211+
} else if oldCondition.Status == corev1.ConditionTrue {
212+
// No longer deprecated at this level; remove the condition
213+
sub.Status.RemoveConditions(conditionType)
214+
changed = true
215+
}
216+
}
217+
218+
if !changed {
219+
// No need to update rollup condition if no other conditions have changed
220+
return false, nil
221+
}
222+
if len(rollupMessages) > 0 {
223+
rollupCondition := v1alpha1.SubscriptionCondition{
224+
Type: v1alpha1.SubscriptionDeprecated,
225+
Message: strings.Join(rollupMessages, "; "),
226+
Status: corev1.ConditionTrue,
227+
LastTransitionTime: c.now(),
228+
}
229+
sub.Status.SetCondition(rollupCondition)
230+
} else {
231+
// No rollup message means no deprecation conditions were set; remove the rollup if it exists
232+
sub.Status.RemoveConditions(v1alpha1.SubscriptionDeprecated)
233+
}
234+
235+
return true, nil
236+
}
237+
112238
// catalogHealth gets the health of catalogs that can affect Susbcriptions in the given namespace.
113239
// This means all catalogs in the given namespace, as well as any catalogs in the operator's global catalog namespace.
114240
func (c *catalogHealthReconciler) catalogHealth(namespace string) ([]v1alpha1.SubscriptionCatalogHealth, error) {

pkg/controller/operators/catalog/subscription/state.go

+7-12
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ type CatalogHealthState interface {
6363
isCatalogHealthState()
6464

6565
// UpdateHealth transitions the CatalogHealthState to another CatalogHealthState based on the given subscription catalog health.
66-
// The state's underlying subscription may be updated on the cluster. If the subscription is updated, the resulting state will contain the updated version.
67-
UpdateHealth(now *metav1.Time, client clientv1alpha1.SubscriptionInterface, health ...v1alpha1.SubscriptionCatalogHealth) (CatalogHealthState, error)
66+
// The state's underlying subscription may be updated. If the subscription is updated, a boolean value of 'true' will be
67+
// returned and the resulting state will contain the updated version.
68+
UpdateHealth(now *metav1.Time, health ...v1alpha1.SubscriptionCatalogHealth) (CatalogHealthState, bool)
6869
}
6970

7071
// CatalogHealthKnownState describes subscription states in which all relevant catalog health is known.
@@ -218,7 +219,7 @@ type catalogHealthState struct {
218219

219220
func (c *catalogHealthState) isCatalogHealthState() {}
220221

221-
func (c *catalogHealthState) UpdateHealth(now *metav1.Time, client clientv1alpha1.SubscriptionInterface, catalogHealth ...v1alpha1.SubscriptionCatalogHealth) (CatalogHealthState, error) {
222+
func (c *catalogHealthState) UpdateHealth(now *metav1.Time, catalogHealth ...v1alpha1.SubscriptionCatalogHealth) (CatalogHealthState, bool) {
222223
in := c.Subscription()
223224
out := in.DeepCopy()
224225

@@ -291,24 +292,18 @@ func (c *catalogHealthState) UpdateHealth(now *metav1.Time, client clientv1alpha
291292

292293
if !update && cond.Equals(in.Status.GetCondition(v1alpha1.SubscriptionCatalogSourcesUnhealthy)) {
293294
// Nothing to do, transition to self
294-
return known, nil
295+
return known, false
295296
}
296297

297298
cond.LastTransitionTime = now
298299
out.Status.LastUpdated = *now
299300
out.Status.SetCondition(cond)
300301
out.Status.CatalogHealth = catalogHealth
301302

302-
updated, err := client.UpdateStatus(context.TODO(), out, metav1.UpdateOptions{})
303-
if err != nil {
304-
// Error occurred, transition to self
305-
return c, err
306-
}
307-
308303
// Inject updated subscription into the state
309-
known.setSubscription(updated)
304+
known.setSubscription(out)
310305

311-
return known, nil
306+
return known, true
312307
}
313308

314309
func NewCatalogHealthState(s SubscriptionExistsState) CatalogHealthState {

pkg/controller/operators/catalog/subscription/state_test.go

+9-11
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestUpdateHealth(t *testing.T) {
3131
type want struct {
3232
transitioned CatalogHealthState
3333
terminal bool
34-
err error
34+
updated bool
3535
}
3636

3737
tests := []struct {
@@ -73,6 +73,7 @@ func TestUpdateHealth(t *testing.T) {
7373
now: &now,
7474
},
7575
want: want{
76+
updated: true,
7677
transitioned: newCatalogUnhealthyState(&v1alpha1.Subscription{
7778
ObjectMeta: metav1.ObjectMeta{
7879
Name: "sub",
@@ -132,6 +133,7 @@ func TestUpdateHealth(t *testing.T) {
132133
},
133134
},
134135
want: want{
136+
updated: true,
135137
transitioned: newCatalogUnhealthyState(&v1alpha1.Subscription{
136138
ObjectMeta: metav1.ObjectMeta{
137139
Name: "sub",
@@ -210,6 +212,7 @@ func TestUpdateHealth(t *testing.T) {
210212
},
211213
},
212214
want: want{
215+
updated: false,
213216
transitioned: newCatalogUnhealthyState(&v1alpha1.Subscription{
214217
ObjectMeta: metav1.ObjectMeta{
215218
Name: "sub",
@@ -289,6 +292,7 @@ func TestUpdateHealth(t *testing.T) {
289292
},
290293
},
291294
want: want{
295+
updated: true,
292296
transitioned: newCatalogHealthyState(&v1alpha1.Subscription{
293297
ObjectMeta: metav1.ObjectMeta{
294298
Name: "sub",
@@ -368,6 +372,7 @@ func TestUpdateHealth(t *testing.T) {
368372
},
369373
},
370374
want: want{
375+
updated: true,
371376
transitioned: newCatalogUnhealthyState(&v1alpha1.Subscription{
372377
ObjectMeta: metav1.ObjectMeta{
373378
Name: "sub",
@@ -447,6 +452,7 @@ func TestUpdateHealth(t *testing.T) {
447452
},
448453
},
449454
want: want{
455+
updated: false,
450456
transitioned: newCatalogHealthyState(&v1alpha1.Subscription{
451457
ObjectMeta: metav1.ObjectMeta{
452458
Name: "sub",
@@ -473,19 +479,11 @@ func TestUpdateHealth(t *testing.T) {
473479

474480
for _, tt := range tests {
475481
t.Run(tt.description, func(t *testing.T) {
476-
fakeClient := tt.fields.existingObjs.fakeClientset(t).OperatorsV1alpha1().Subscriptions(tt.fields.namespace)
477-
transitioned, err := tt.fields.state.UpdateHealth(tt.args.now, fakeClient, tt.args.catalogHealth...)
478-
require.Equal(t, tt.want.err, err)
482+
transitioned, updated := tt.fields.state.UpdateHealth(tt.args.now, tt.args.catalogHealth...)
479483
require.Equal(t, tt.want.transitioned, transitioned)
480-
484+
require.Equal(t, tt.want.updated, updated)
481485
if tt.want.transitioned != nil {
482486
require.Equal(t, tt.want.terminal, transitioned.Terminal())
483-
484-
// Ensure the client's view of the subscription matches the typestate's
485-
sub := transitioned.(SubscriptionState).Subscription()
486-
clusterSub, err := fakeClient.Get(context.TODO(), sub.GetName(), metav1.GetOptions{})
487-
require.NoError(t, err)
488-
require.Equal(t, sub, clusterSub)
489487
}
490488
})
491489
}

pkg/controller/operators/catalog/subscription/syncer.go

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/operator-framework/api/pkg/operators/install"
1414
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1515
listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
16+
resolverCache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
1617
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
1718
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
1819
"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
@@ -34,6 +35,7 @@ type subscriptionSyncer struct {
3435
installPlanLister listers.InstallPlanLister
3536
globalCatalogNamespace string
3637
notify kubestate.NotifyFunc
38+
sourceProvider resolverCache.SourceProvider
3739
}
3840

3941
// now returns the Syncer's current time.
@@ -214,6 +216,7 @@ func newSyncerWithConfig(ctx context.Context, config *syncerConfig) (kubestate.S
214216
reconcilers: config.reconcilers,
215217
subscriptionCache: config.subscriptionInformer.GetIndexer(),
216218
installPlanLister: config.lister.OperatorsV1alpha1().InstallPlanLister(),
219+
sourceProvider: config.sourceProvider,
217220
notify: func(event kubestate.ResourceEvent) {
218221
// Notify Subscriptions by enqueuing to the Subscription queue.
219222
config.subscriptionQueue.Add(event)
@@ -234,6 +237,7 @@ func newSyncerWithConfig(ctx context.Context, config *syncerConfig) (kubestate.S
234237
catalogLister: config.lister.OperatorsV1alpha1().CatalogSourceLister(),
235238
registryReconcilerFactory: config.registryReconcilerFactory,
236239
globalCatalogNamespace: config.globalCatalogNamespace,
240+
sourceProvider: config.sourceProvider,
237241
},
238242
}
239243
s.reconcilers = append(defaultReconcilers, s.reconcilers...)

0 commit comments

Comments
 (0)