1
1
package applier
2
2
3
3
import (
4
+ "cmp"
4
5
"context"
5
6
"crypto/sha256"
6
7
"encoding/hex"
@@ -9,7 +10,6 @@ import (
9
10
"io/fs"
10
11
"maps"
11
12
"slices"
12
- "sort"
13
13
14
14
"github.com/davecgh/go-spew/spew"
15
15
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,53 +26,39 @@ import (
26
26
)
27
27
28
28
const (
29
- revisionHashAnnotation = "olm.operatorframework.io/hash"
30
- revisionHistoryLimit = 5
29
+ RevisionHashAnnotation = "olm.operatorframework.io/hash"
30
+ // revisionHistoryLimit = 5
31
31
)
32
32
33
- type Boxcutter struct {
34
- Client client.Client
35
- Scheme * runtime.Scheme
36
- BundleRenderer render.BundleRenderer
33
+ type ClusterExtensionRevisionGenerator interface {
34
+ GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels map [string ]string ) (* ocv1.ClusterExtensionRevision , error )
37
35
}
38
36
39
- func (bc * Boxcutter ) Apply (
40
- ctx context.Context , contentFS fs.FS ,
41
- ext * ocv1.ClusterExtension ,
42
- objectLabels , storageLabels map [string ]string ,
43
- ) ([]client.Object , string , error ) {
44
- objs , err := bc .apply (ctx , contentFS , ext , objectLabels , storageLabels )
45
- return objs , "" , err
37
+ type SimpleRevisionGenerator struct {
38
+ Scheme * runtime.Scheme
39
+ BundleRenderer BundleRenderer
46
40
}
47
41
48
- func (bc * Boxcutter ) apply (
49
- ctx context.Context , contentFS fs.FS ,
50
- ext * ocv1.ClusterExtension ,
51
- objectLabels , _ map [string ]string ,
52
- ) ([]client.Object , error ) {
53
- reg , err := source .FromFS (contentFS ).GetBundle ()
54
- if err != nil {
55
- return nil , err
56
- }
57
-
58
- watchNamespace , err := GetWatchNamespace (ext )
59
- if err != nil {
60
- return nil , err
61
- }
62
-
63
- plain , err := bc .BundleRenderer .Render (reg , ext .Spec .Namespace , render .WithTargetNamespaces (watchNamespace ))
42
+ func (r * SimpleRevisionGenerator ) GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels map [string ]string ) (* ocv1.ClusterExtensionRevision , error ) {
43
+ // extract plain manifests
44
+ plain , err := r .BundleRenderer .Render (bundleFS , ext )
64
45
if err != nil {
65
46
return nil , err
66
47
}
67
48
68
49
// objectLabels
69
50
objs := make ([]ocv1.ClusterExtensionRevisionObject , 0 , len (plain ))
70
51
for _ , obj := range plain {
71
- labels := maps .Clone (obj .GetLabels ())
72
- maps .Copy (labels , objectLabels )
73
- obj .SetLabels (labels )
52
+ if len (obj .GetLabels ()) > 0 {
53
+ labels := maps .Clone (obj .GetLabels ())
54
+ if labels == nil {
55
+ labels = map [string ]string {}
56
+ }
57
+ maps .Copy (labels , objectLabels )
58
+ obj .SetLabels (labels )
59
+ }
74
60
75
- gvk , err := apiutil .GVKForObject (obj , bc .Scheme )
61
+ gvk , err := apiutil .GVKForObject (obj , r .Scheme )
76
62
if err != nil {
77
63
return nil , err
78
64
}
@@ -89,63 +75,77 @@ func (bc *Boxcutter) apply(
89
75
})
90
76
}
91
77
92
- // List all existing revisions
93
- existingRevisionList := & ocv1.ClusterExtensionRevisionList {}
94
- if err := bc .Client .List (ctx , existingRevisionList , client.MatchingLabels {
95
- controllers .ClusterExtensionRevisionOwnerLabel : ext .Name ,
96
- }); err != nil {
97
- return nil , fmt .Errorf ("listing revisions: %w" , err )
98
- }
99
- sort .Sort (revisionAscending (existingRevisionList .Items ))
100
- existingRevisions := existingRevisionList .Items
101
-
102
78
// Build desired revision
103
- desiredRevision := & ocv1.ClusterExtensionRevision {
79
+ return & ocv1.ClusterExtensionRevision {
104
80
ObjectMeta : metav1.ObjectMeta {
105
81
Annotations : map [string ]string {},
106
82
Labels : map [string ]string {
107
83
controllers .ClusterExtensionRevisionOwnerLabel : ext .Name ,
108
84
},
109
85
},
110
86
Spec : ocv1.ClusterExtensionRevisionSpec {
111
- Revision : 1 ,
112
87
Phases : []ocv1.ClusterExtensionRevisionPhase {
113
88
{
114
89
Name : "everything" ,
115
90
Objects : objs ,
116
91
},
117
92
},
118
93
},
94
+ }, nil
95
+ }
96
+
97
+ type Boxcutter struct {
98
+ Client client.Client
99
+ Scheme * runtime.Scheme
100
+ RevisionGenerator ClusterExtensionRevisionGenerator
101
+ }
102
+
103
+ func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , storageLabels map [string ]string ) ([]client.Object , string , error ) {
104
+ objs , err := bc .apply (ctx , contentFS , ext , objectLabels , storageLabels )
105
+ return objs , "" , err
106
+ }
107
+
108
+ func (bc * Boxcutter ) apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , _ map [string ]string ) ([]client.Object , error ) {
109
+ // Generate desired revision
110
+ desiredRevision , err := bc .RevisionGenerator .GenerateRevision (contentFS , ext , objectLabels )
111
+ if err != nil {
112
+ return nil , err
113
+ }
114
+
115
+ // List all existing revisions
116
+ existingRevisions , err := bc .getExistingRevisions (ctx , ext .GetName ())
117
+ if err != nil {
118
+ return nil , err
119
119
}
120
120
desiredHash := computeSHA256Hash (desiredRevision .Spec .Phases )
121
121
122
122
// Sort into current and previous revisions.
123
123
var (
124
124
currentRevision * ocv1.ClusterExtensionRevision
125
- prevRevisions []ocv1.ClusterExtensionRevision
125
+ // prevRevisions []ocv1.ClusterExtensionRevision
126
126
)
127
127
if len (existingRevisions ) > 0 {
128
128
maybeCurrentRevision := existingRevisions [len (existingRevisions )- 1 ]
129
-
130
129
annotations := maybeCurrentRevision .GetAnnotations ()
131
130
if annotations != nil {
132
- if hash , ok := annotations [revisionHashAnnotation ]; ok &&
133
- hash == desiredHash {
131
+ if revisionHash , ok := annotations [RevisionHashAnnotation ]; ok && revisionHash == desiredHash {
134
132
currentRevision = & maybeCurrentRevision
135
- prevRevisions = existingRevisions [0 : len (existingRevisions )- 1 ] // previous is everything excluding current
133
+ // prevRevisions = existingRevisions[0 : len(existingRevisions)-1] // previous is everything excluding current
136
134
}
137
135
}
138
136
}
139
137
140
138
if currentRevision == nil {
141
139
// all Revisions are outdated => create a new one.
142
- prevRevisions = existingRevisions
143
- revisionNumber := latestRevisionNumber (prevRevisions )
144
- revisionNumber ++
140
+ prevRevisions := existingRevisions
141
+ revisionNumber := latestRevisionNumber (prevRevisions ) + 1
145
142
146
143
newRevision := desiredRevision
147
144
newRevision .Name = fmt .Sprintf ("%s-%d" , ext .Name , revisionNumber )
148
- newRevision .Annotations [revisionHashAnnotation ] = desiredHash
145
+ if newRevision .GetAnnotations () == nil {
146
+ newRevision .Annotations = map [string ]string {}
147
+ }
148
+ newRevision .Annotations [RevisionHashAnnotation ] = desiredHash
149
149
newRevision .Spec .Revision = revisionNumber
150
150
for _ , prevRevision := range prevRevisions {
151
151
newRevision .Spec .Previous = append (newRevision .Spec .Previous , ocv1.ClusterExtensionRevisionPrevious {
@@ -163,25 +163,46 @@ func (bc *Boxcutter) apply(
163
163
}
164
164
165
165
// Delete archived previous revisions over revisionHistory limit
166
- numToDelete := len (prevRevisions ) - revisionHistoryLimit
167
- slices .Reverse (prevRevisions )
166
+ //numToDelete := len(prevRevisions) - revisionHistoryLimit
167
+ //slices.Reverse(prevRevisions)
168
+ //
169
+ //for _, prevRev := range prevRevisions {
170
+ // if numToDelete <= 0 {
171
+ // break
172
+ // }
173
+ //
174
+ // if err := client.IgnoreNotFound(bc.Client.Delete(ctx, &prevRev)); err != nil {
175
+ // return nil, fmt.Errorf("failed to delete revision (history limit): %w", err)
176
+ // }
177
+ // numToDelete--
178
+ //}
168
179
169
- for _ , prevRev := range prevRevisions {
170
- if numToDelete <= 0 {
171
- break
172
- }
180
+ // TODO: Read status from revision.
173
181
174
- if err := client .IgnoreNotFound (bc .Client .Delete (ctx , & prevRev )); err != nil {
175
- return nil , fmt .Errorf ("failed to delete revision (history limit): %w" , err )
182
+ // Collect objects
183
+ var plain []client.Object
184
+ for _ , phase := range desiredRevision .Spec .Phases {
185
+ for _ , phaseObject := range phase .Objects {
186
+ plain = append (plain , & phaseObject .Object )
176
187
}
177
- numToDelete --
178
188
}
179
-
180
- // TODO: Read status from revision.
181
-
182
189
return plain , nil
183
190
}
184
191
192
+ // getExistingRevisions returns the list of ClusterExtensionRevisions for a ClusterExtension with name extName in revision order (oldest to newest)
193
+ func (bc * Boxcutter ) getExistingRevisions (ctx context.Context , extName string ) ([]ocv1.ClusterExtensionRevision , error ) {
194
+ existingRevisionList := & ocv1.ClusterExtensionRevisionList {}
195
+ if err := bc .Client .List (ctx , existingRevisionList , client.MatchingLabels {
196
+ controllers .ClusterExtensionRevisionOwnerLabel : extName ,
197
+ }); err != nil {
198
+ return nil , fmt .Errorf ("listing revisions: %w" , err )
199
+ }
200
+ slices .SortFunc (existingRevisionList .Items , func (a , b ocv1.ClusterExtensionRevision ) int {
201
+ return cmp .Compare (a .Spec .Revision , b .Spec .Revision )
202
+ })
203
+ return existingRevisionList .Items , nil
204
+ }
205
+
185
206
// computeSHA256Hash returns a sha236 hash value calculated from object.
186
207
func computeSHA256Hash (obj any ) string {
187
208
hasher := sha256 .New ()
@@ -206,21 +227,29 @@ func deepHashObject(hasher hash.Hash, objectToWrite any) {
206
227
}
207
228
}
208
229
209
- type revisionAscending []ocv1.ClusterExtensionRevision
210
-
211
- func (a revisionAscending ) Len () int { return len (a ) }
212
- func (a revisionAscending ) Swap (i , j int ) { a [i ], a [j ] = a [j ], a [i ] }
213
- func (a revisionAscending ) Less (i , j int ) bool {
214
- iObj := a [i ]
215
- jObj := a [j ]
216
-
217
- return iObj .Spec .Revision < jObj .Spec .Revision
218
- }
219
-
220
230
func latestRevisionNumber (prevRevisions []ocv1.ClusterExtensionRevision ) int64 {
221
231
if len (prevRevisions ) == 0 {
222
232
return 0
223
233
}
224
-
225
234
return prevRevisions [len (prevRevisions )- 1 ].Spec .Revision
226
235
}
236
+
237
+ type BundleRenderer interface {
238
+ Render (bundleFS fs.FS , ext * ocv1.ClusterExtension ) ([]client.Object , error )
239
+ }
240
+
241
+ type RegistryV1BundleRenderer struct {
242
+ BundleRenderer render.BundleRenderer
243
+ }
244
+
245
+ func (r * RegistryV1BundleRenderer ) Render (bundleFS fs.FS , ext * ocv1.ClusterExtension ) ([]client.Object , error ) {
246
+ reg , err := source .FromFS (bundleFS ).GetBundle ()
247
+ if err != nil {
248
+ return nil , err
249
+ }
250
+ watchNamespace , err := GetWatchNamespace (ext )
251
+ if err != nil {
252
+ return nil , err
253
+ }
254
+ return r .BundleRenderer .Render (reg , ext .Spec .Namespace , render .WithTargetNamespaces (watchNamespace ))
255
+ }
0 commit comments