Skip to content

Commit 12e578b

Browse files
committed
Propagate ClusterExtensionRevision conditions to ClusterExtension status
* `ClusterExtension`'s `Available` and `Progressing` conditions are equal to the counterparts on the latest installed active revision * When a new revision is rolling out, mirror its `Progressing` condition to its counterpart on `ClusterExtension` to improve visibility and to signal that a transition is in progress * Adds `.status.activeRevisions` field to the `ClusterExtension` to track the active revisions and mirrors `Available` and `Progressing` conditions for those not marked yet as installed This changes opens up a possibility for an observer to get insights on `ClusterExtension` update process: * When Reason on `Progressing` condition flips to `RollingOut`, an update is in progress * More then one revision could be listed under `.status.activeRevisions` during upgrade (installed + those in progress) * `Available` and `Progressing` conditions of an active, but not yet available revision, are listed under `.status.activeRevisions.conditions` so that can be easier to understand update issues The new behavior is marked as experimental behind Boxcutter feature gate.
1 parent f17f3c5 commit 12e578b

File tree

20 files changed

+650
-8
lines changed

20 files changed

+650
-8
lines changed

api/v1/clusterextension_types.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,17 @@ type BundleMetadata struct {
472472
Version string `json:"version"`
473473
}
474474

475+
// RevisionStatus defines the observed state of a ClusterExtensionRevision.
476+
type RevisionStatus struct {
477+
// name is the revision name that can be used to retrieve it.
478+
Name string `json:"name"`
479+
// Conditions is a set of conditions associated with the revision.
480+
// +listType=map
481+
// +listMapKey=type
482+
// +optional
483+
Conditions []metav1.Condition `json:"conditions,omitempty"`
484+
}
485+
475486
// ClusterExtensionStatus defines the observed state of a ClusterExtension.
476487
type ClusterExtensionStatus struct {
477488
// The set of condition types which apply to all spec.source variations are Installed and Progressing.
@@ -504,6 +515,13 @@ type ClusterExtensionStatus struct {
504515
//
505516
// +optional
506517
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
518+
519+
// activeRevisions is a list of currently active ClusterExtensionRevisions.
520+
// +listType=map
521+
// +listMapKey=name
522+
// +optional
523+
// <opcon:experimental>
524+
ActiveRevisions []RevisionStatus `json:"activeRevisions,omitempty"`
507525
}
508526

509527
// ClusterExtensionInstallStatus is a representation of the status of the identified bundle.
@@ -523,6 +541,7 @@ type ClusterExtensionInstallStatus struct {
523541
// +kubebuilder:printcolumn:name="Installed Bundle",type=string,JSONPath=`.status.install.bundle.name`
524542
// +kubebuilder:printcolumn:name=Version,type=string,JSONPath=`.status.install.bundle.version`
525543
// +kubebuilder:printcolumn:name="Installed",type=string,JSONPath=`.status.conditions[?(@.type=='Installed')].status`
544+
// +kubebuilder:printcolumn:name="Available",type=string,JSONPath=`.status.conditions[?(@.type=='Available')].status`
526545
// +kubebuilder:printcolumn:name="Progressing",type=string,JSONPath=`.status.conditions[?(@.type=='Progressing')].status`
527546
// +kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp`
528547

api/v1/zz_generated.deepcopy.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/operator-controller/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
628628
controllers.RetrieveRevisionStates(revisionStatesGetter),
629629
controllers.ResolveBundle(c.resolver),
630630
controllers.UnpackBundle(c.imagePuller, c.imageCache),
631-
controllers.ApplyBundle(appl),
631+
controllers.ApplyBundleWithBoxcutter(appl),
632632
}
633633

634634
baseDiscoveryClient, err := discovery.NewDiscoveryClientForConfig(c.mgr.GetConfig())

docs/api-reference/olmv1-api-reference.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ _Appears in:_
361361
| --- | --- | --- | --- |
362362
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | The set of condition types which apply to all spec.source variations are Installed and Progressing.<br />The Installed condition represents whether or not the bundle has been installed for this ClusterExtension.<br />When Installed is True and the Reason is Succeeded, the bundle has been successfully installed.<br />When Installed is False and the Reason is Failed, the bundle has failed to install.<br />The Progressing condition represents whether or not the ClusterExtension is advancing towards a new state.<br />When Progressing is True and the Reason is Succeeded, the ClusterExtension is making progress towards a new state.<br />When Progressing is True and the Reason is Retrying, the ClusterExtension has encountered an error that could be resolved on subsequent reconciliation attempts.<br />When Progressing is False and the Reason is Blocked, the ClusterExtension has encountered an error that requires manual intervention for recovery.<br /><opcon:experimental:description><br />When Progressing is True and Reason is RollingOut, the ClusterExtension has one or more ClusterExtensionRevisions in active roll out.<br /></opcon:experimental:description><br />When the ClusterExtension is sourced from a catalog, if may also communicate a deprecation condition.<br />These are indications from a package owner to guide users away from a particular package, channel, or bundle.<br />BundleDeprecated is set if the requested bundle version is marked deprecated in the catalog.<br />ChannelDeprecated is set if the requested channel is marked deprecated in the catalog.<br />PackageDeprecated is set if the requested package is marked deprecated in the catalog.<br />Deprecated is a rollup condition that is present when any of the deprecated conditions are present. | | |
363363
| `install` _[ClusterExtensionInstallStatus](#clusterextensioninstallstatus)_ | install is a representation of the current installation status for this ClusterExtension. | | |
364+
| `activeRevisions` _[RevisionStatus](#revisionstatus) array_ | activeRevisions is a list of currently active ClusterExtensionRevisions.<br /><opcon:experimental> | | |
364365

365366

366367

@@ -435,6 +436,23 @@ _Appears in:_
435436
| `ref` _string_ | ref contains the resolved image digest-based reference.<br />The digest format is used so users can use other tooling to fetch the exact<br />OCI manifests that were used to extract the catalog contents. | | MaxLength: 1000 <br />Required: \{\} <br /> |
436437

437438

439+
#### RevisionStatus
440+
441+
442+
443+
RevisionStatus defines the observed state of a ClusterExtensionRevision.
444+
445+
446+
447+
_Appears in:_
448+
- [ClusterExtensionStatus](#clusterextensionstatus)
449+
450+
| Field | Description | Default | Validation |
451+
| --- | --- | --- | --- |
452+
| `name` _string_ | name is the revision name that can be used to retrieve it. | | |
453+
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions is a set of conditions associated with the revision. | | |
454+
455+
438456
#### ServiceAccountReference
439457

440458

helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ spec:
2525
- jsonPath: .status.conditions[?(@.type=='Installed')].status
2626
name: Installed
2727
type: string
28+
- jsonPath: .status.conditions[?(@.type=='Available')].status
29+
name: Available
30+
type: string
2831
- jsonPath: .status.conditions[?(@.type=='Progressing')].status
2932
name: Progressing
3033
type: string
@@ -506,6 +509,84 @@ spec:
506509
description: status is an optional field that defines the observed state
507510
of the ClusterExtension.
508511
properties:
512+
activeRevisions:
513+
description: activeRevisions is a list of currently active ClusterExtensionRevisions.
514+
items:
515+
description: RevisionStatus defines the observed state of a ClusterExtensionRevision.
516+
properties:
517+
conditions:
518+
description: Conditions is a set of conditions associated with
519+
the revision.
520+
items:
521+
description: Condition contains details for one aspect of
522+
the current state of this API Resource.
523+
properties:
524+
lastTransitionTime:
525+
description: |-
526+
lastTransitionTime is the last time the condition transitioned from one status to another.
527+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
528+
format: date-time
529+
type: string
530+
message:
531+
description: |-
532+
message is a human readable message indicating details about the transition.
533+
This may be an empty string.
534+
maxLength: 32768
535+
type: string
536+
observedGeneration:
537+
description: |-
538+
observedGeneration represents the .metadata.generation that the condition was set based upon.
539+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
540+
with respect to the current state of the instance.
541+
format: int64
542+
minimum: 0
543+
type: integer
544+
reason:
545+
description: |-
546+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
547+
Producers of specific condition types may define expected values and meanings for this field,
548+
and whether the values are considered a guaranteed API.
549+
The value should be a CamelCase string.
550+
This field may not be empty.
551+
maxLength: 1024
552+
minLength: 1
553+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
554+
type: string
555+
status:
556+
description: status of the condition, one of True, False,
557+
Unknown.
558+
enum:
559+
- "True"
560+
- "False"
561+
- Unknown
562+
type: string
563+
type:
564+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
565+
maxLength: 316
566+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
567+
type: string
568+
required:
569+
- lastTransitionTime
570+
- message
571+
- reason
572+
- status
573+
- type
574+
type: object
575+
type: array
576+
x-kubernetes-list-map-keys:
577+
- type
578+
x-kubernetes-list-type: map
579+
name:
580+
description: name is the revision name that can be used to retrieve
581+
it.
582+
type: string
583+
required:
584+
- name
585+
type: object
586+
type: array
587+
x-kubernetes-list-map-keys:
588+
- name
589+
x-kubernetes-list-type: map
509590
conditions:
510591
description: |-
511592
The set of condition types which apply to all spec.source variations are Installed and Progressing.

helm/olmv1/base/operator-controller/crd/standard/olm.operatorframework.io_clusterextensions.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ spec:
2525
- jsonPath: .status.conditions[?(@.type=='Installed')].status
2626
name: Installed
2727
type: string
28+
- jsonPath: .status.conditions[?(@.type=='Available')].status
29+
name: Available
30+
type: string
2831
- jsonPath: .status.conditions[?(@.type=='Progressing')].status
2932
name: Progressing
3033
type: string

internal/operator-controller/applier/boxcutter.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,6 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
399399
default:
400400
return false, progressingCondition.Message, nil
401401
}
402-
} else if availableCondition != nil && availableCondition.Status != metav1.ConditionTrue {
403-
return false, "", errors.New(availableCondition.Message)
404402
} else if succeededCondition != nil && succeededCondition.Status != metav1.ConditionTrue {
405403
return false, succeededCondition.Message, nil
406404
}

internal/operator-controller/controllers/boxcutter_reconcile_steps.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
apimeta "k8s.io/apimachinery/pkg/api/meta"
2626
ctrl "sigs.k8s.io/controller-runtime"
2727
"sigs.k8s.io/controller-runtime/pkg/client"
28+
"sigs.k8s.io/controller-runtime/pkg/log"
2829

2930
ocv1 "github.com/operator-framework/operator-controller/api/v1"
3031
"github.com/operator-framework/operator-controller/internal/operator-controller/labels"
@@ -54,12 +55,14 @@ func (d *BoxcutterRevisionStatesGetter) GetRevisionStates(ctx context.Context, e
5455
continue
5556
}
5657

57-
// TODO: the setting of these annotations (happens in boxcutter applier when we pass in "revisionAnnotations")
58+
// TODO: the setting of these annotations (happens in boxcutter applier when we pass in "storageLabels")
5859
// is fairly decoupled from this code where we get the annotations back out. We may want to co-locate
5960
// the set/get logic a bit better to make it more maintainable and less likely to get out of sync.
6061
rm := &RevisionMetadata{
61-
Package: rev.Annotations[labels.PackageNameKey],
62-
Image: rev.Annotations[labels.BundleReferenceKey],
62+
RevName: rev.Name,
63+
Package: rev.Labels[labels.PackageNameKey],
64+
Image: rev.Annotations[labels.BundleReferenceKey],
65+
Conditions: rev.Status.Conditions,
6366
BundleMetadata: ocv1.BundleMetadata{
6467
Name: rev.Annotations[labels.BundleNameKey],
6568
Version: rev.Annotations[labels.BundleVersionKey],
@@ -89,3 +92,55 @@ func MigrateStorage(m StorageMigrator) ReconcileStepFunc {
8992
return nil, nil
9093
}
9194
}
95+
96+
func ApplyBundleWithBoxcutter(a Applier) ReconcileStepFunc {
97+
return func(ctx context.Context, state *reconcileState, ext *ocv1.ClusterExtension) (*ctrl.Result, error) {
98+
l := log.FromContext(ctx)
99+
storeLbls := map[string]string{
100+
labels.BundleNameKey: state.resolvedRevisionMetadata.Name,
101+
labels.PackageNameKey: state.resolvedRevisionMetadata.Package,
102+
labels.BundleVersionKey: state.resolvedRevisionMetadata.Version,
103+
labels.BundleReferenceKey: state.resolvedRevisionMetadata.Image,
104+
}
105+
objLbls := map[string]string{
106+
labels.OwnerKindKey: ocv1.ClusterExtensionKind,
107+
labels.OwnerNameKey: ext.GetName(),
108+
}
109+
110+
l.Info("applying bundle contents")
111+
if _, _, err := a.Apply(ctx, state.imageFS, ext, objLbls, storeLbls); err != nil {
112+
// If there was an error applying the resolved bundle,
113+
// report the error via the Progressing condition.
114+
setStatusProgressing(ext, wrapErrorWithResolutionInfo(state.resolvedRevisionMetadata.BundleMetadata, err))
115+
return nil, err
116+
}
117+
118+
// Mirror Available/Progressing conditions from the installed revision
119+
if i := state.revisionStates.Installed; i != nil {
120+
for _, cndType := range []string{ocv1.ClusterExtensionRevisionTypeAvailable, ocv1.ClusterExtensionRevisionTypeProgressing} {
121+
if cnd := apimeta.FindStatusCondition(i.Conditions, cndType); cnd != nil {
122+
cnd.ObservedGeneration = ext.GetGeneration()
123+
apimeta.SetStatusCondition(&ext.Status.Conditions, *cnd)
124+
}
125+
}
126+
ext.Status.Install = &ocv1.ClusterExtensionInstallStatus{
127+
Bundle: i.BundleMetadata,
128+
}
129+
ext.Status.ActiveRevisions = []ocv1.RevisionStatus{{Name: i.RevName}}
130+
}
131+
for idx, r := range state.revisionStates.RollingOut {
132+
rs := ocv1.RevisionStatus{Name: r.RevName, Conditions: r.Conditions}
133+
// Mirror Progressing condition from the latest active revision
134+
if idx == len(state.revisionStates.RollingOut)-1 {
135+
if pcnd := apimeta.FindStatusCondition(r.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing); pcnd != nil {
136+
pcnd.ObservedGeneration = ext.GetGeneration()
137+
apimeta.SetStatusCondition(&ext.Status.Conditions, *pcnd)
138+
}
139+
}
140+
ext.Status.ActiveRevisions = append(ext.Status.ActiveRevisions, rs)
141+
}
142+
143+
setInstalledStatusFromRevisionStates(ext, state.revisionStates)
144+
return nil, nil
145+
}
146+
}

internal/operator-controller/controllers/clusterextension_controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,11 @@ func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) crh
332332
}
333333

334334
type RevisionMetadata struct {
335+
RevName string
335336
Package string
336337
Image string
337338
ocv1.BundleMetadata
339+
Conditions []metav1.Condition
338340
}
339341

340342
type RevisionStates struct {

0 commit comments

Comments
 (0)