Skip to content

Commit 3816d20

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 ff7d3c2 commit 3816d20

File tree

17 files changed

+655
-7
lines changed

17 files changed

+655
-7
lines changed

api/v1/clusterextension_types.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,21 @@ 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 of the ClusterExtensionRevision resource
478+
Name string `json:"name"`
479+
// conditions optionally expose Progressing and Available condition of the revision,
480+
// in case when it is not yet marked as successfully installed (condition Succeeded is not set to True).
481+
// Given that a ClusterExtension should remain available during upgrades, an observer may use these conditions
482+
// to get more insights about reasons for its current state.
483+
//
484+
// +listType=map
485+
// +listMapKey=type
486+
// +optional
487+
Conditions []metav1.Condition `json:"conditions,omitempty"`
488+
}
489+
475490
// ClusterExtensionStatus defines the observed state of a ClusterExtension.
476491
type ClusterExtensionStatus struct {
477492
// The set of condition types which apply to all spec.source variations are Installed and Progressing.
@@ -504,6 +519,14 @@ type ClusterExtensionStatus struct {
504519
//
505520
// +optional
506521
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
522+
523+
// activeRevisions holds a list of currently active (non-archived) ClusterExtensionRevisions,
524+
// including both installed and rolling out revisions.
525+
// +listType=map
526+
// +listMapKey=name
527+
// +optional
528+
// <opcon:experimental>
529+
ActiveRevisions []RevisionStatus `json:"activeRevisions,omitempty"`
507530
}
508531

509532
// ClusterExtensionInstallStatus is a representation of the status of the identified bundle.

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 holds a list of currently active (non-archived) ClusterExtensionRevisions,<br />including both installed and rolling out revisions.<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 of the ClusterExtensionRevision resource | | |
453+
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | conditions optionally expose Progressing and Available condition of the revision,<br />in case when it is not yet marked as successfully installed (condition Succeeded is not set to True).<br />Given that a ClusterExtension should remain available during upgrades, an observer may use these conditions<br />to get more insights about reasons for its current state. | | |
454+
455+
438456
#### ServiceAccountReference
439457

440458

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,88 @@ spec:
506506
description: status is an optional field that defines the observed state
507507
of the ClusterExtension.
508508
properties:
509+
activeRevisions:
510+
description: |-
511+
activeRevisions holds a list of currently active (non-archived) ClusterExtensionRevisions,
512+
including both installed and rolling out revisions.
513+
items:
514+
description: RevisionStatus defines the observed state of a ClusterExtensionRevision.
515+
properties:
516+
conditions:
517+
description: |-
518+
conditions optionally expose Progressing and Available condition of the revision,
519+
in case when it is not yet marked as successfully installed (condition Succeeded is not set to True).
520+
Given that a ClusterExtension should remain available during upgrades, an observer may use these conditions
521+
to get more insights about reasons for its current state.
522+
items:
523+
description: Condition contains details for one aspect of
524+
the current state of this API Resource.
525+
properties:
526+
lastTransitionTime:
527+
description: |-
528+
lastTransitionTime is the last time the condition transitioned from one status to another.
529+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
530+
format: date-time
531+
type: string
532+
message:
533+
description: |-
534+
message is a human readable message indicating details about the transition.
535+
This may be an empty string.
536+
maxLength: 32768
537+
type: string
538+
observedGeneration:
539+
description: |-
540+
observedGeneration represents the .metadata.generation that the condition was set based upon.
541+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
542+
with respect to the current state of the instance.
543+
format: int64
544+
minimum: 0
545+
type: integer
546+
reason:
547+
description: |-
548+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
549+
Producers of specific condition types may define expected values and meanings for this field,
550+
and whether the values are considered a guaranteed API.
551+
The value should be a CamelCase string.
552+
This field may not be empty.
553+
maxLength: 1024
554+
minLength: 1
555+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
556+
type: string
557+
status:
558+
description: status of the condition, one of True, False,
559+
Unknown.
560+
enum:
561+
- "True"
562+
- "False"
563+
- Unknown
564+
type: string
565+
type:
566+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
567+
maxLength: 316
568+
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])$
569+
type: string
570+
required:
571+
- lastTransitionTime
572+
- message
573+
- reason
574+
- status
575+
- type
576+
type: object
577+
type: array
578+
x-kubernetes-list-map-keys:
579+
- type
580+
x-kubernetes-list-type: map
581+
name:
582+
description: name of the ClusterExtensionRevision resource
583+
type: string
584+
required:
585+
- name
586+
type: object
587+
type: array
588+
x-kubernetes-list-map-keys:
589+
- name
590+
x-kubernetes-list-type: map
509591
conditions:
510592
description: |-
511593
The set of condition types which apply to all spec.source variations are Installed and Progressing.

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: 63 additions & 2 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"
@@ -58,8 +59,10 @@ func (d *BoxcutterRevisionStatesGetter) GetRevisionStates(ctx context.Context, e
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+
RevisionName: rev.Name,
63+
Package: rev.Annotations[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,61 @@ 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+
revisionAnnotations := 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, revisionAnnotations); 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.RevisionName}}
130+
}
131+
for idx, r := range state.revisionStates.RollingOut {
132+
rs := ocv1.RevisionStatus{Name: r.RevisionName}
133+
for _, cndType := range []string{ocv1.ClusterExtensionRevisionTypeAvailable, ocv1.ClusterExtensionRevisionTypeProgressing} {
134+
if cnd := apimeta.FindStatusCondition(r.Conditions, cndType); cnd != nil {
135+
cnd.ObservedGeneration = ext.GetGeneration()
136+
apimeta.SetStatusCondition(&rs.Conditions, *cnd)
137+
}
138+
}
139+
// Mirror Progressing condition from the latest active revision
140+
if idx == len(state.revisionStates.RollingOut)-1 {
141+
if pcnd := apimeta.FindStatusCondition(r.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing); pcnd != nil {
142+
pcnd.ObservedGeneration = ext.GetGeneration()
143+
apimeta.SetStatusCondition(&ext.Status.Conditions, *pcnd)
144+
}
145+
}
146+
ext.Status.ActiveRevisions = append(ext.Status.ActiveRevisions, rs)
147+
}
148+
149+
setInstalledStatusFromRevisionStates(ext, state.revisionStates)
150+
return nil, nil
151+
}
152+
}

internal/operator-controller/controllers/clusterextension_controller.go

Lines changed: 4 additions & 2 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-
Package string
336-
Image string
335+
RevisionName string
336+
Package string
337+
Image string
337338
ocv1.BundleMetadata
339+
Conditions []metav1.Condition
338340
}
339341

340342
type RevisionStates struct {

0 commit comments

Comments
 (0)