Skip to content

Commit 0c41e3e

Browse files
authored
✨improve Extension status conditions (#683)
* map kapp conditions to Extension Signed-off-by: Ankita Thomas <[email protected]> * add Progressing condition Signed-off-by: Ankita Thomas <[email protected]> * remove changes to deprecation statuses Signed-off-by: Ankita Thomas <[email protected]> --------- Signed-off-by: Ankita Thomas <[email protected]>
1 parent 652e4bc commit 0c41e3e

File tree

5 files changed

+134
-48
lines changed

5 files changed

+134
-48
lines changed

api/v1alpha1/clusterextension_types_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestClusterExtensionTypeRegistration(t *testing.T) {
2222
}
2323

2424
for _, tt := range types {
25-
if !slices.Contains(conditionsets.ConditionTypes, tt) {
25+
if !slices.Contains(conditionsets.ConditionTypes, tt) && !slices.Contains(conditionsets.ExtensionConditionTypes, tt) {
2626
t.Errorf("append Type%s to conditionsets.ConditionTypes in this package's init function", tt)
2727
}
2828
}
@@ -32,6 +32,11 @@ func TestClusterExtensionTypeRegistration(t *testing.T) {
3232
t.Errorf("there must be a Type%[1]s string literal constant for type %[1]q (i.e. 'const Type%[1]s = %[1]q')", tt)
3333
}
3434
}
35+
for _, tt := range conditionsets.ExtensionConditionTypes {
36+
if !slices.Contains(types, tt) {
37+
t.Errorf("there must be a Type%[1]s string literal constant for type %[1]q (i.e. 'const Type%[1]s = %[1]q')", tt)
38+
}
39+
}
3540
}
3641

3742
func TestClusterExtensionReasonRegistration(t *testing.T) {
@@ -41,7 +46,7 @@ func TestClusterExtensionReasonRegistration(t *testing.T) {
4146
}
4247

4348
for _, r := range reasons {
44-
if !slices.Contains(conditionsets.ConditionReasons, r) {
49+
if !slices.Contains(conditionsets.ConditionReasons, r) && !slices.Contains(conditionsets.ExtensionConditionReasons, r) {
4550
t.Errorf("append Reason%s to conditionsets.ConditionReasons in this package's init function.", r)
4651
}
4752
}
@@ -50,6 +55,11 @@ func TestClusterExtensionReasonRegistration(t *testing.T) {
5055
t.Errorf("there must be a Reason%[1]s string literal constant for reason %[1]q (i.e. 'const Reason%[1]s = %[1]q')", r)
5156
}
5257
}
58+
for _, r := range conditionsets.ExtensionConditionReasons {
59+
if !slices.Contains(reasons, r) {
60+
t.Errorf("there must be a Reason%[1]s string literal constant for reason %[1]q (i.e. 'const Reason%[1]s = %[1]q')", r)
61+
}
62+
}
5363
}
5464

5565
// parseConstants parses the values of the top-level constants in the current

api/v1alpha1/extension_types.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package v1alpha1
1818

1919
import (
2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
22+
"github.com/operator-framework/operator-controller/internal/conditionsets"
2123
)
2224

2325
const (
@@ -133,6 +135,25 @@ type ExtensionList struct {
133135
Items []Extension `json:"items"`
134136
}
135137

138+
const (
139+
// TypeProgressing indicates whether operator-controller is
140+
// reconciling, installing, updating or deleting an extension.
141+
TypeProgressing = "Progressing"
142+
143+
ReasonProgressing = "Progressing"
144+
ReasonFailedToReachDesiredIntent = "FailedToReachDesiredIntent"
145+
ReasonReachedDesiredIntent = "ReachedDesiredIntent"
146+
)
147+
136148
func init() {
137149
SchemeBuilder.Register(&Extension{}, &ExtensionList{})
150+
151+
conditionsets.ExtensionConditionTypes = []string{
152+
TypeProgressing,
153+
}
154+
conditionsets.ExtensionConditionReasons = []string{
155+
ReasonProgressing,
156+
ReasonFailedToReachDesiredIntent,
157+
ReasonReachedDesiredIntent,
158+
}
138159
}

internal/conditionsets/conditionsets.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ package conditionsets
2222
// NOTE: These are populated by init() in api/v1alpha1/clusterextension_types.go
2323
var ConditionTypes []string
2424
var ConditionReasons []string
25+
26+
// ExtensionConditionTypes is the set of Extension exclusive condition Types.
27+
var ExtensionConditionTypes []string
28+
var ExtensionConditionReasons []string

internal/controllers/common_controller.go

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,6 @@ func setResolvedStatusConditionFailed(conditions *[]metav1.Condition, message st
6565
})
6666
}
6767

68-
// setResolvedStatusConditionUnknown sets the resolved status condition to unknown.
69-
func setResolvedStatusConditionUnknown(conditions *[]metav1.Condition, message string, generation int64) {
70-
apimeta.SetStatusCondition(conditions, metav1.Condition{
71-
Type: ocv1alpha1.TypeResolved,
72-
Status: metav1.ConditionUnknown,
73-
Reason: ocv1alpha1.ReasonResolutionUnknown,
74-
Message: message,
75-
ObservedGeneration: generation,
76-
})
77-
}
78-
7968
// setInstalledStatusConditionSuccess sets the installed status condition to success.
8069
func setInstalledStatusConditionSuccess(conditions *[]metav1.Condition, message string, generation int64) {
8170
apimeta.SetStatusCondition(conditions, metav1.Condition{
@@ -117,3 +106,36 @@ func setDeprecationStatusesUnknown(conditions *[]metav1.Condition, message strin
117106
})
118107
}
119108
}
109+
110+
// setProgressingStatusConditionSuccess sets the progressing status condition to false for a successful install or upgrade.
111+
func setProgressingStatusConditionSuccess(conditions *[]metav1.Condition, message string, generation int64) {
112+
apimeta.SetStatusCondition(conditions, metav1.Condition{
113+
Type: ocv1alpha1.TypeProgressing,
114+
Status: metav1.ConditionFalse,
115+
Reason: ocv1alpha1.ReasonReachedDesiredIntent,
116+
Message: message,
117+
ObservedGeneration: generation,
118+
})
119+
}
120+
121+
// setProgressingStatusConditionFailed sets the progressing status condition to False for a failed install or upgrade.
122+
func setProgressingStatusConditionFailed(conditions *[]metav1.Condition, message string, generation int64) {
123+
apimeta.SetStatusCondition(conditions, metav1.Condition{
124+
Type: ocv1alpha1.TypeProgressing,
125+
Status: metav1.ConditionFalse,
126+
Reason: ocv1alpha1.ReasonFailedToReachDesiredIntent,
127+
Message: message,
128+
ObservedGeneration: generation,
129+
})
130+
}
131+
132+
// setProgressingStatusConditionProgressing sets the progressing status condition to true for an app being reconciled.
133+
func setProgressingStatusConditionProgressing(conditions *[]metav1.Condition, message string, generation int64) {
134+
apimeta.SetStatusCondition(conditions, metav1.Condition{
135+
Type: ocv1alpha1.TypeProgressing,
136+
Status: metav1.ConditionTrue,
137+
Reason: ocv1alpha1.ReasonProgressing,
138+
Message: message,
139+
ObservedGeneration: generation,
140+
})
141+
}

internal/controllers/extension_controller.go

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,14 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext
127127
if !features.OperatorControllerFeatureGate.Enabled(features.EnableExtensionAPI) {
128128
l.Info("extension feature is gated", "name", ext.GetName(), "namespace", ext.GetNamespace())
129129

130-
// Set the TypeInstalled condition to Unknown to indicate that the resolution
130+
// Set the TypeInstalled condition to Failed to indicate that the resolution
131131
// hasn't been attempted yet, due to the spec being invalid.
132132
ext.Status.InstalledBundle = nil
133-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, "extension feature is disabled", ext.GetGeneration())
134-
// Set the TypeResolved condition to Unknown to indicate that the resolution
133+
setInstalledStatusConditionFailed(&ext.Status.Conditions, "extension feature is disabled", ext.GetGeneration())
134+
// Set the TypeResolved condition to Failed to indicate that the resolution
135135
// hasn't been attempted yet, due to the spec being invalid.
136136
ext.Status.ResolvedBundle = nil
137-
setResolvedStatusConditionUnknown(&ext.Status.Conditions, "extension feature is disabled", ext.GetGeneration())
137+
setResolvedStatusConditionFailed(&ext.Status.Conditions, "extension feature is disabled", ext.GetGeneration())
138138

139139
setDeprecationStatusesUnknown(&ext.Status.Conditions, "extension feature is disabled", ext.GetGeneration())
140140
return ctrl.Result{}, nil
@@ -150,8 +150,10 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext
150150
// TODO: Improve the resolution logic.
151151
bundle, err := r.resolve(ctx, *ext)
152152
if err != nil {
153-
ext.Status.InstalledBundle = nil
154-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, "installation has not been attempted as resolution failed", ext.GetGeneration())
153+
if c := apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled); c == nil {
154+
ext.Status.InstalledBundle = nil
155+
setInstalledStatusConditionFailed(&ext.Status.Conditions, "installation has not been attempted as resolution failed", ext.GetGeneration())
156+
}
155157
ext.Status.ResolvedBundle = nil
156158
setResolvedStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
157159
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as resolution failed", ext.GetGeneration())
@@ -164,33 +166,46 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext
164166

165167
mediaType, err := bundle.MediaType()
166168
if err != nil {
167-
setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
168-
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
169+
if c := apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled); c == nil {
170+
ext.Status.InstalledBundle = nil
171+
setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("failed to read bundle mediaType: %v", err), ext.GetGeneration())
172+
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
173+
}
174+
setProgressingStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("failed to read bundle mediaType: %v", err), ext.GetGeneration())
169175
return ctrl.Result{}, err
170176
}
171177

172178
// TODO: this needs to include the registryV1 bundle option. As of this PR, this only supports direct
173179
// installation of a set of manifests.
174180
if mediaType != catalogmetadata.MediaTypePlain {
175-
// Set the TypeInstalled condition to Unknown to indicate that the resolution
176-
// hasn't been attempted yet, due to the spec being invalid.
177-
ext.Status.InstalledBundle = nil
178-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, fmt.Sprintf("bundle type %s not supported currently", mediaType), ext.GetGeneration())
179-
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
181+
if c := apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled); c == nil {
182+
// Set the TypeInstalled condition to Failed to indicate that the resolution
183+
// hasn't been attempted yet, due to the spec being invalid.
184+
ext.Status.InstalledBundle = nil
185+
setInstalledStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("bundle type %s not supported currently", mediaType), ext.GetGeneration())
186+
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
187+
}
188+
setProgressingStatusConditionFailed(&ext.Status.Conditions, fmt.Sprintf("bundle type %s not supported currently", mediaType), ext.GetGeneration())
180189
return ctrl.Result{}, nil
181190
}
182191

183192
app, err := r.GenerateExpectedApp(*ext, bundle)
184193
if err != nil {
185-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
186-
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
194+
if c := apimeta.FindStatusCondition(ext.Status.Conditions, ocv1alpha1.TypeInstalled); c == nil {
195+
ext.Status.InstalledBundle = nil
196+
setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
197+
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
198+
}
199+
setProgressingStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
187200
return ctrl.Result{}, err
188201
}
189202

190203
if err := r.ensureApp(ctx, app); err != nil {
191204
// originally Reason: ocv1alpha1.ReasonInstallationFailed
192205
ext.Status.InstalledBundle = nil
193206
setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
207+
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
208+
setProgressingStatusConditionProgressing(&ext.Status.Conditions, "installation failed", ext.GetGeneration())
194209
return ctrl.Result{}, err
195210
}
196211

@@ -199,14 +214,19 @@ func (r *ExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.Ext
199214
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(app.UnstructuredContent(), existingTypedApp); err != nil {
200215
// originally Reason: ocv1alpha1.ReasonInstallationStatusUnknown
201216
ext.Status.InstalledBundle = nil
202-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
217+
setInstalledStatusConditionFailed(&ext.Status.Conditions, err.Error(), ext.GetGeneration())
203218
setDeprecationStatusesUnknown(&ext.Status.Conditions, "deprecation checks have not been attempted as installation has failed", ext.GetGeneration())
219+
setProgressingStatusConditionProgressing(&ext.Status.Conditions, "installation failed", ext.GetGeneration())
204220
return ctrl.Result{}, err
205221
}
206222

207-
mapAppStatusToInstalledCondition(existingTypedApp, ext, bundle)
223+
ext.Status.InstalledBundle = bundleMetadataFor(bundle)
224+
setInstalledStatusConditionSuccess(&ext.Status.Conditions, fmt.Sprintf("successfully installed %v", ext.Status.InstalledBundle), ext.GetGeneration())
208225
SetDeprecationStatusInExtension(ext, bundle)
209226

227+
// TODO: add conditions to determine extension health
228+
mapAppStatusToCondition(existingTypedApp, ext)
229+
210230
return ctrl.Result{}, nil
211231
}
212232

@@ -228,28 +248,37 @@ func (r *ExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
228248
Complete(r)
229249
}
230250

231-
// TODO: follow up with mapping of all the available App statuses: https://github.com/carvel-dev/kapp-controller/blob/855063edee53315811a13ee8d5df1431ba258ede/pkg/apis/kappctrl/v1alpha1/status.go#L28-L35
232-
// mapAppStatusToInstalledCondition currently maps only the installed condition.
233-
func mapAppStatusToInstalledCondition(existingApp *kappctrlv1alpha1.App, ext *ocv1alpha1.Extension, bundle *catalogmetadata.Bundle) {
234-
appReady := findStatusCondition(existingApp.Status.GenericStatus.Conditions, kappctrlv1alpha1.ReconcileSucceeded)
235-
if appReady == nil {
236-
ext.Status.InstalledBundle = nil
237-
setInstalledStatusConditionUnknown(&ext.Status.Conditions, "install status unknown", ext.Generation)
251+
// mapAppStatusToCondition maps the reconciling/deleting App conditions to the installed/deleting conditions on the Extension.
252+
func mapAppStatusToCondition(existingApp *kappctrlv1alpha1.App, ext *ocv1alpha1.Extension) {
253+
// Note: App.Status.Inspect errors are never surfaced to App conditions, so are currently ignored when determining App status.
254+
if ext == nil || existingApp == nil {
238255
return
239256
}
240-
241-
if appReady.Status != corev1.ConditionTrue {
242-
ext.Status.InstalledBundle = nil
243-
setInstalledStatusConditionFailed(
244-
&ext.Status.Conditions,
245-
appReady.Message,
246-
ext.GetGeneration(),
247-
)
248-
return
257+
message := existingApp.Status.FriendlyDescription
258+
if len(message) == 0 || strings.Contains(message, "Error (see .status.usefulErrorMessage for details)") {
259+
message = existingApp.Status.UsefulErrorMessage
249260
}
250261

251-
ext.Status.InstalledBundle = bundleMetadataFor(bundle)
252-
setInstalledStatusConditionSuccess(&ext.Status.Conditions, appReady.Message, ext.Generation)
262+
appStatusMapFn := map[kappctrlv1alpha1.ConditionType]func(*[]metav1.Condition, string, int64){
263+
kappctrlv1alpha1.Deleting: setProgressingStatusConditionProgressing,
264+
kappctrlv1alpha1.Reconciling: setProgressingStatusConditionProgressing,
265+
kappctrlv1alpha1.DeleteFailed: setProgressingStatusConditionFailed,
266+
kappctrlv1alpha1.ReconcileFailed: setProgressingStatusConditionFailed,
267+
kappctrlv1alpha1.ReconcileSucceeded: setProgressingStatusConditionSuccess,
268+
}
269+
for cond := range appStatusMapFn {
270+
if c := findStatusCondition(existingApp.Status.GenericStatus.Conditions, cond); c != nil && c.Status == corev1.ConditionTrue {
271+
if len(message) == 0 {
272+
message = c.Message
273+
}
274+
appStatusMapFn[cond](&ext.Status.Conditions, fmt.Sprintf("App %s: %s", c.Type, message), ext.Generation)
275+
return
276+
}
277+
}
278+
if len(message) == 0 {
279+
message = "waiting for app"
280+
}
281+
setProgressingStatusConditionProgressing(&ext.Status.Conditions, message, ext.Generation)
253282
}
254283

255284
// setDeprecationStatus will set the appropriate deprecation statuses for a Extension

0 commit comments

Comments
 (0)