Skip to content

Commit 35cdf3b

Browse files
author
Per Goncalves da Silva
committed
Progress
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 26b052f commit 35cdf3b

File tree

7 files changed

+836
-124
lines changed

7 files changed

+836
-124
lines changed

config/base/operator-controller/rbac/experimental/role.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ rules:
2727
- apiGroups:
2828
- olm.operatorframework.io
2929
resources:
30-
- clusterextensions
30+
- clusterextensionrevisions
3131
verbs:
32+
- create
33+
- delete
3234
- get
3335
- list
3436
- patch
@@ -37,16 +39,28 @@ rules:
3739
- apiGroups:
3840
- olm.operatorframework.io
3941
resources:
42+
- clusterextensionrevisions/finalizers
4043
- clusterextensions/finalizers
4144
verbs:
4245
- update
4346
- apiGroups:
4447
- olm.operatorframework.io
4548
resources:
49+
- clusterextensionrevisions/status
4650
- clusterextensions/status
4751
verbs:
4852
- patch
4953
- update
54+
- apiGroups:
55+
- olm.operatorframework.io
56+
resources:
57+
- clusterextensions
58+
verbs:
59+
- get
60+
- list
61+
- patch
62+
- update
63+
- watch
5064
- apiGroups:
5165
- rbac.authorization.k8s.io
5266
resources:
Lines changed: 108 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package applier
22

33
import (
4+
"cmp"
45
"context"
56
"crypto/sha256"
67
"encoding/hex"
@@ -9,7 +10,6 @@ import (
910
"io/fs"
1011
"maps"
1112
"slices"
12-
"sort"
1313

1414
"github.com/davecgh/go-spew/spew"
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -26,53 +26,39 @@ import (
2626
)
2727

2828
const (
29-
revisionHashAnnotation = "olm.operatorframework.io/hash"
30-
revisionHistoryLimit = 5
29+
RevisionHashAnnotation = "olm.operatorframework.io/hash"
30+
// revisionHistoryLimit = 5
3131
)
3232

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)
3735
}
3836

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
4640
}
4741

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)
6445
if err != nil {
6546
return nil, err
6647
}
6748

6849
// objectLabels
6950
objs := make([]ocv1.ClusterExtensionRevisionObject, 0, len(plain))
7051
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+
}
7460

75-
gvk, err := apiutil.GVKForObject(obj, bc.Scheme)
61+
gvk, err := apiutil.GVKForObject(obj, r.Scheme)
7662
if err != nil {
7763
return nil, err
7864
}
@@ -89,63 +75,77 @@ func (bc *Boxcutter) apply(
8975
})
9076
}
9177

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-
10278
// Build desired revision
103-
desiredRevision := &ocv1.ClusterExtensionRevision{
79+
return &ocv1.ClusterExtensionRevision{
10480
ObjectMeta: metav1.ObjectMeta{
10581
Annotations: map[string]string{},
10682
Labels: map[string]string{
10783
controllers.ClusterExtensionRevisionOwnerLabel: ext.Name,
10884
},
10985
},
11086
Spec: ocv1.ClusterExtensionRevisionSpec{
111-
Revision: 1,
11287
Phases: []ocv1.ClusterExtensionRevisionPhase{
11388
{
11489
Name: "everything",
11590
Objects: objs,
11691
},
11792
},
11893
},
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
119119
}
120120
desiredHash := computeSHA256Hash(desiredRevision.Spec.Phases)
121121

122122
// Sort into current and previous revisions.
123123
var (
124124
currentRevision *ocv1.ClusterExtensionRevision
125-
prevRevisions []ocv1.ClusterExtensionRevision
125+
// prevRevisions []ocv1.ClusterExtensionRevision
126126
)
127127
if len(existingRevisions) > 0 {
128128
maybeCurrentRevision := existingRevisions[len(existingRevisions)-1]
129-
130129
annotations := maybeCurrentRevision.GetAnnotations()
131130
if annotations != nil {
132-
if hash, ok := annotations[revisionHashAnnotation]; ok &&
133-
hash == desiredHash {
131+
if revisionHash, ok := annotations[RevisionHashAnnotation]; ok && revisionHash == desiredHash {
134132
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
136134
}
137135
}
138136
}
139137

140138
if currentRevision == nil {
141139
// 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
145142

146143
newRevision := desiredRevision
147144
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
149149
newRevision.Spec.Revision = revisionNumber
150150
for _, prevRevision := range prevRevisions {
151151
newRevision.Spec.Previous = append(newRevision.Spec.Previous, ocv1.ClusterExtensionRevisionPrevious{
@@ -163,25 +163,46 @@ func (bc *Boxcutter) apply(
163163
}
164164

165165
// 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+
//}
168179

169-
for _, prevRev := range prevRevisions {
170-
if numToDelete <= 0 {
171-
break
172-
}
180+
// TODO: Read status from revision.
173181

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)
176187
}
177-
numToDelete--
178188
}
179-
180-
// TODO: Read status from revision.
181-
182189
return plain, nil
183190
}
184191

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+
185206
// computeSHA256Hash returns a sha236 hash value calculated from object.
186207
func computeSHA256Hash(obj any) string {
187208
hasher := sha256.New()
@@ -206,21 +227,29 @@ func deepHashObject(hasher hash.Hash, objectToWrite any) {
206227
}
207228
}
208229

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-
220230
func latestRevisionNumber(prevRevisions []ocv1.ClusterExtensionRevision) int64 {
221231
if len(prevRevisions) == 0 {
222232
return 0
223233
}
224-
225234
return prevRevisions[len(prevRevisions)-1].Spec.Revision
226235
}
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

Comments
 (0)