@@ -29,7 +29,6 @@ import (
2929 "github.com/operator-framework/operator-controller/internal/operator-controller/labels"
3030 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
3131 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
32- hashutil "github.com/operator-framework/operator-controller/internal/shared/util/hash"
3332)
3433
3534const (
@@ -202,6 +201,7 @@ type Boxcutter struct {
202201 Scheme * runtime.Scheme
203202 RevisionGenerator ClusterExtensionRevisionGenerator
204203 Preflights []Preflight
204+ FieldOwner string
205205}
206206
207207func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (bool , string , error ) {
@@ -225,77 +225,98 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
225225 return false , "" , err
226226 }
227227
228+ gvk , err := apiutil .GVKForObject (desiredRevision , bc .Scheme )
229+ if err != nil {
230+ return false , "" , fmt .Errorf ("getting GVK of ClusterExtensionRevision failed: %w" , err )
231+ }
232+
233+ if err := controllerutil .SetControllerReference (ext , desiredRevision , bc .Scheme ); err != nil {
234+ return false , "" , fmt .Errorf ("set ownerref: %w" , err )
235+ }
236+
228237 // List all existing revisions
229238 existingRevisions , err := bc .getExistingRevisions (ctx , ext .GetName ())
230239 if err != nil {
231240 return false , "" , err
232241 }
233- desiredHash := hashutil .DeepHashObject (desiredRevision .Spec .Phases )
234242
235- // Sort into current and previous revisions.
236- var (
237- currentRevision * ocv1.ClusterExtensionRevision
238- )
243+ currentRevision := & ocv1.ClusterExtensionRevision {}
239244 state := StateNeedsInstall
240- if len (existingRevisions ) > 0 {
241- maybeCurrentRevision := existingRevisions [len (existingRevisions )- 1 ]
242- annotations := maybeCurrentRevision .GetAnnotations ()
243- if annotations != nil {
244- if revisionHash , ok := annotations [RevisionHashAnnotation ]; ok && revisionHash == desiredHash {
245- currentRevision = & maybeCurrentRevision
245+ // check if we can update the current revision.
246+ if len (existingRevisions ) > 0 { // nolint:nestif
247+ // try first to update the current revision.
248+ currentRevision = & existingRevisions [len (existingRevisions )- 1 ]
249+ desiredRevision .Spec .Previous = currentRevision .Spec .Previous
250+ desiredRevision .Spec .Revision = currentRevision .Spec .Revision
251+ desiredRevision .Name = currentRevision .Name
252+
253+ obj , err := runtime .DefaultUnstructuredConverter .ToUnstructured (desiredRevision )
254+ if err != nil {
255+ return false , "" , fmt .Errorf ("converting ClusterExtensionRevision to unstructured failed: %w" , err )
256+ }
257+ udr := & unstructured.Unstructured {Object : obj }
258+ udr .SetGroupVersionKind (gvk )
259+ if err = bc .Client .Patch (ctx , udr , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership ); err != nil {
260+ if ! apierrors .IsInvalid (err ) {
261+ return false , "" , fmt .Errorf ("patching %s Revision: %w" , desiredRevision .Name , err )
246262 }
263+ // We could not update the current revision due to trying to update an immutable field.
264+ // Therefore, we need to create a new revision.
265+ state = StateNeedsUpgrade
266+ } else {
267+ // inplace patch was successful, no changes in phases
268+ state = StateUnchanged
247269 }
248- state = StateNeedsUpgrade
249270 }
250271
251- // Preflights
252- plainObjs := bc .getObjects (desiredRevision )
253- for _ , preflight := range bc .Preflights {
254- if shouldSkipPreflight (ctx , preflight , ext , state ) {
255- continue
256- }
257- switch state {
258- case StateNeedsInstall :
259- err := preflight .Install (ctx , plainObjs )
260- if err != nil {
261- return false , "" , err
272+ if state != StateUnchanged { // nolint:nestif
273+ // Preflights
274+ plainObjs := bc .getObjects (desiredRevision )
275+ for _ , preflight := range bc .Preflights {
276+ if shouldSkipPreflight (ctx , preflight , ext , state ) {
277+ continue
262278 }
263- // TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but
264- // it isn't immediately obvious why that is. Perhaps len(existingRevisions) is
265- // always greater than 0 (seems unlikely), or shouldSkipPreflight always returns
266- // true (and we continue) when state == StateNeedsInstall?
267- case StateNeedsUpgrade :
268- err := preflight .Upgrade (ctx , plainObjs )
269- if err != nil {
270- return false , "" , err
279+ switch state {
280+ case StateNeedsInstall :
281+ err := preflight .Install (ctx , plainObjs )
282+ if err != nil {
283+ return false , "" , err
284+ }
285+ // TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but
286+ // it isn't immediately obvious why that is. Perhaps len(existingRevisions) is
287+ // always greater than 0 (seems unlikely), or shouldSkipPreflight always returns
288+ // true (and we continue) when state == StateNeedsInstall?
289+ case StateNeedsUpgrade :
290+ err := preflight .Upgrade (ctx , plainObjs )
291+ if err != nil {
292+ return false , "" , err
293+ }
271294 }
272295 }
273- }
274296
275- if currentRevision == nil {
276- // all Revisions are outdated => create a new one.
297+ // need to create new revision
277298 prevRevisions := existingRevisions
278299 revisionNumber := latestRevisionNumber (prevRevisions ) + 1
279300
280- newRevision := desiredRevision
281- newRevision .Name = fmt .Sprintf ("%s-%d" , ext .Name , revisionNumber )
282- if newRevision .GetAnnotations () == nil {
283- newRevision .Annotations = map [string ]string {}
284- }
285- newRevision .Annotations [RevisionHashAnnotation ] = desiredHash
286- newRevision .Spec .Revision = revisionNumber
301+ desiredRevision .Name = fmt .Sprintf ("%s-%d" , ext .Name , revisionNumber )
302+ desiredRevision .Spec .Revision = revisionNumber
287303
288- if err = bc .setPreviousRevisions (ctx , newRevision , prevRevisions ); err != nil {
304+ if err = bc .setPreviousRevisions (ctx , desiredRevision , prevRevisions ); err != nil {
289305 return false , "" , fmt .Errorf ("garbage collecting old Revisions: %w" , err )
290306 }
291307
292- if err := controllerutil .SetControllerReference (ext , newRevision , bc .Scheme ); err != nil {
293- return false , "" , fmt .Errorf ("set ownerref: %w" , err )
308+ obj , err := runtime .DefaultUnstructuredConverter .ToUnstructured (desiredRevision )
309+ if err != nil {
310+ return false , "" , fmt .Errorf ("converting ClusterExtensionRevision to unstructured failed: %w" , err )
294311 }
295- if err := bc .Client .Create (ctx , newRevision ); err != nil {
312+ udr := & unstructured.Unstructured {Object : obj }
313+ udr .SetGroupVersionKind (gvk )
314+ if err := bc .Client .Patch (ctx , udr , client .Apply , client .FieldOwner (bc .FieldOwner ), client .ForceOwnership ); err != nil {
296315 return false , "" , fmt .Errorf ("creating new Revision: %w" , err )
297316 }
298- currentRevision = newRevision
317+ if err := runtime .DefaultUnstructuredConverter .FromUnstructured (udr .Object , currentRevision ); err != nil {
318+ return false , "" , fmt .Errorf ("converting unstructured to ClusterExtensionRevision failed: %w" , err )
319+ }
299320 }
300321
301322 progressingCondition := meta .FindStatusCondition (currentRevision .Status .Conditions , ocv1 .TypeProgressing )
0 commit comments