@@ -10,17 +10,23 @@ import (
1010 "hash"
1111 "io/fs"
1212 "maps"
13+ "regexp"
1314 "slices"
15+ "strings"
1416
1517 "github.com/davecgh/go-spew/spew"
18+ "helm.sh/helm/v3/pkg/release"
19+ "helm.sh/helm/v3/pkg/storage/driver"
1620 "k8s.io/apimachinery/pkg/api/meta"
1721 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1822 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1923 "k8s.io/apimachinery/pkg/runtime"
2024 "sigs.k8s.io/controller-runtime/pkg/client"
2125 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2226 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
27+ "sigs.k8s.io/yaml"
2328
29+ helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2430 ocv1 "github.com/operator-framework/operator-controller/api/v1"
2531 "github.com/operator-framework/operator-controller/internal/operator-controller/controllers"
2632 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
@@ -33,13 +39,57 @@ const (
3339
3440type ClusterExtensionRevisionGenerator interface {
3541 GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (* ocv1.ClusterExtensionRevision , error )
42+ GenerateRevisionFromHelmRelease (
43+ helmRelease * release.Release , ext * ocv1.ClusterExtension ,
44+ objectLabels , revisionAnnotations map [string ]string ,
45+ ) (* ocv1.ClusterExtensionRevision , error )
3646}
3747
3848type SimpleRevisionGenerator struct {
3949 Scheme * runtime.Scheme
4050 BundleRenderer BundleRenderer
4151}
4252
53+ func (r * SimpleRevisionGenerator ) GenerateRevisionFromHelmRelease (
54+ helmRelease * release.Release , ext * ocv1.ClusterExtension ,
55+ objectLabels , revisionAnnotations map [string ]string ,
56+ ) (* ocv1.ClusterExtensionRevision , error ) {
57+ docs := splitYAMLDocuments (helmRelease .Manifest )
58+ objs := make ([]ocv1.ClusterExtensionRevisionObject , 0 , len (docs ))
59+ for _ , doc := range docs {
60+ obj := unstructured.Unstructured {}
61+ if err := yaml .Unmarshal ([]byte (doc ), & obj ); err != nil {
62+ return nil , err
63+ }
64+
65+ labels := maps .Clone (obj .GetLabels ())
66+ if labels == nil {
67+ labels = map [string ]string {}
68+ }
69+ maps .Copy (labels , objectLabels )
70+ obj .SetLabels (labels )
71+
72+ objs = append (objs , ocv1.ClusterExtensionRevisionObject {Object : obj })
73+ }
74+
75+ if revisionAnnotations == nil {
76+ revisionAnnotations = map [string ]string {}
77+ }
78+
79+ // Build desired revision
80+ return & ocv1.ClusterExtensionRevision {
81+ ObjectMeta : metav1.ObjectMeta {
82+ Annotations : revisionAnnotations ,
83+ Labels : map [string ]string {
84+ controllers .ClusterExtensionRevisionOwnerLabel : ext .Name ,
85+ },
86+ },
87+ Spec : ocv1.ClusterExtensionRevisionSpec {
88+ Phases : PhaseSort (objs ),
89+ },
90+ }, nil
91+ }
92+
4393func (r * SimpleRevisionGenerator ) GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (* ocv1.ClusterExtensionRevision , error ) {
4494 // extract plain manifests
4595 plain , err := r .BundleRenderer .Render (bundleFS , ext )
@@ -50,14 +100,12 @@ func (r *SimpleRevisionGenerator) GenerateRevision(bundleFS fs.FS, ext *ocv1.Clu
50100 // objectLabels
51101 objs := make ([]ocv1.ClusterExtensionRevisionObject , 0 , len (plain ))
52102 for _ , obj := range plain {
53- if len (obj .GetLabels ()) > 0 {
54- labels := maps .Clone (obj .GetLabels ())
55- if labels == nil {
56- labels = map [string ]string {}
57- }
58- maps .Copy (labels , objectLabels )
59- obj .SetLabels (labels )
103+ labels := maps .Clone (obj .GetLabels ())
104+ if labels == nil {
105+ labels = map [string ]string {}
60106 }
107+ maps .Copy (labels , objectLabels )
108+ obj .SetLabels (labels )
61109
62110 gvk , err := apiutil .GVKForObject (obj , r .Scheme )
63111 if err != nil {
@@ -99,6 +147,13 @@ type Boxcutter struct {
99147 Scheme * runtime.Scheme
100148 RevisionGenerator ClusterExtensionRevisionGenerator
101149 Preflights []Preflight
150+
151+ // For Migration:
152+ ActionClientGetter helmclient.ActionClientGetter
153+ }
154+
155+ type helmReleaseGetter interface {
156+ Get (name string , opts ... helmclient.GetOption ) (* release.Release , error )
102157}
103158
104159func (bc * Boxcutter ) Apply (ctx context.Context , contentFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (bool , string , error ) {
@@ -145,6 +200,20 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
145200 state = StateNeedsUpgrade
146201 }
147202
203+ if currentRevision == nil && len (existingRevisions ) == 0 {
204+ // Helm Migration
205+ // There are no existing revisions, so maybe we have used the Helm Applier in the past.
206+ // Check if there is a Helm Release on for the ClusterExtension and migrate it into an existing revision.
207+ rev , ok , err := bc .migrateFromHelm (ctx , ext , objectLabels , revisionAnnotations )
208+ if err != nil {
209+ return false , "" , err
210+ }
211+
212+ if ok {
213+ desiredRevision = rev
214+ }
215+ }
216+
148217 // Preflights
149218 plainObjs := bc .getObjects (desiredRevision )
150219 for _ , preflight := range bc .Preflights {
@@ -216,6 +285,43 @@ func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.Clust
216285 return true , "" , nil
217286}
218287
288+ func (bc * Boxcutter ) migrateFromHelm (
289+ ctx context.Context , ext * ocv1.ClusterExtension ,
290+ objectLabels , revisionAnnotations map [string ]string ,
291+ ) (
292+ * ocv1.ClusterExtensionRevision , bool , error ,
293+ ) {
294+ ac , err := bc .ActionClientGetter .ActionClientFor (ctx , ext )
295+ if err != nil {
296+ return nil , false , err
297+ }
298+
299+ helmRelease , err := ac .Get (ext .GetName ())
300+ if errors .Is (err , driver .ErrReleaseNotFound ) {
301+ // no Helm Release -> no prior installation.
302+ return nil , false , nil
303+ }
304+ if err != nil {
305+ return nil , false , err
306+ }
307+
308+ docs := splitYAMLDocuments (helmRelease .Manifest )
309+ objs := make ([]unstructured.Unstructured , 0 , len (docs ))
310+ for _ , doc := range docs {
311+ obj := unstructured.Unstructured {}
312+ if err := yaml .Unmarshal ([]byte (doc ), & obj ); err != nil {
313+ return nil , false , err
314+ }
315+ objs = append (objs , obj )
316+ }
317+
318+ rev , err := bc .RevisionGenerator .GenerateRevisionFromHelmRelease (helmRelease , ext , objectLabels , revisionAnnotations )
319+ if err != nil {
320+ return nil , false , err
321+ }
322+ return rev , true , nil
323+ }
324+
219325// getExistingRevisions returns the list of ClusterExtensionRevisions for a ClusterExtension with name extName in revision order (oldest to newest)
220326func (bc * Boxcutter ) getExistingRevisions (ctx context.Context , extName string ) ([]ocv1.ClusterExtensionRevision , error ) {
221327 existingRevisionList := & ocv1.ClusterExtensionRevisionList {}
@@ -289,3 +395,13 @@ func (r *RegistryV1BundleRenderer) Render(bundleFS fs.FS, ext *ocv1.ClusterExten
289395 }
290396 return r .BundleRenderer .Render (reg , ext .Spec .Namespace , render .WithTargetNamespaces (watchNamespace ), render .WithCertificateProvider (r .CertificateProvider ))
291397}
398+
399+ var splitYAMLDocumentsRegEx = regexp .MustCompile (`(?m)^---$` )
400+
401+ // Splits a YAML file into multiple documents.
402+ func splitYAMLDocuments (file string ) (docs []string ) {
403+ for _ , yamlDocument := range splitYAMLDocumentsRegEx .Split (string (strings .Trim (file , "---\n " )), - 1 ) {
404+ docs = append (docs , strings .TrimSpace (string (yamlDocument )))
405+ }
406+ return docs
407+ }
0 commit comments