@@ -11,18 +11,25 @@ import (
1111 "io/fs"
1212 "maps"
1313 "slices"
14+ "strings"
1415
1516 "github.com/davecgh/go-spew/spew"
17+ "helm.sh/helm/v3/pkg/release"
18+ "helm.sh/helm/v3/pkg/storage/driver"
1619 "k8s.io/apimachinery/pkg/api/meta"
1720 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1821 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1922 "k8s.io/apimachinery/pkg/runtime"
2023 "sigs.k8s.io/controller-runtime/pkg/client"
2124 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2225 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
26+ "sigs.k8s.io/yaml"
27+
28+ helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
2329
2430 ocv1 "github.com/operator-framework/operator-controller/api/v1"
2531 "github.com/operator-framework/operator-controller/internal/operator-controller/controllers"
32+ "github.com/operator-framework/operator-controller/internal/operator-controller/labels"
2633 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
2734 "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
2835)
@@ -33,14 +40,58 @@ const (
3340
3441type ClusterExtensionRevisionGenerator interface {
3542 GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (* ocv1.ClusterExtensionRevision , error )
43+ GenerateRevisionFromHelmRelease (
44+ helmRelease * release.Release , ext * ocv1.ClusterExtension ,
45+ objectLabels map [string ]string ,
46+ ) (* ocv1.ClusterExtensionRevision , error )
3647}
3748
3849type SimpleRevisionGenerator struct {
3950 Scheme * runtime.Scheme
4051 BundleRenderer BundleRenderer
4152}
4253
43- func (r * SimpleRevisionGenerator ) GenerateRevision (bundleFS fs.FS , ext * ocv1.ClusterExtension , objectLabels , revisionAnnotations map [string ]string ) (* ocv1.ClusterExtensionRevision , error ) {
54+ func (r * SimpleRevisionGenerator ) GenerateRevisionFromHelmRelease (
55+ helmRelease * release.Release , ext * ocv1.ClusterExtension ,
56+ objectLabels map [string ]string ,
57+ ) (* ocv1.ClusterExtensionRevision , error ) {
58+ docs := splitManifestDocuments (helmRelease .Manifest )
59+ objs := make ([]ocv1.ClusterExtensionRevisionObject , 0 , len (docs ))
60+ for _ , doc := range docs {
61+ obj := unstructured.Unstructured {}
62+ if err := yaml .Unmarshal ([]byte (doc ), & obj ); err != nil {
63+ return nil , err
64+ }
65+
66+ labels := maps .Clone (obj .GetLabels ())
67+ if labels == nil {
68+ labels = map [string ]string {}
69+ }
70+ maps .Copy (labels , objectLabels )
71+ obj .SetLabels (labels )
72+ obj .SetOwnerReferences (nil ) // reset OwnerReferences for migration.
73+
74+ objs = append (objs , ocv1.ClusterExtensionRevisionObject {
75+ Object : obj ,
76+ CollisionProtection : ocv1 .CollisionProtectionNone , // allow to adopt objects from previous release
77+ })
78+ }
79+
80+ rev := r .buildClusterExtensionRevision (objs , ext , map [string ]string {
81+ labels .BundleNameKey : helmRelease .Labels [labels .BundleNameKey ],
82+ labels .PackageNameKey : helmRelease .Labels [labels .PackageNameKey ],
83+ labels .BundleVersionKey : helmRelease .Labels [labels .BundleVersionKey ],
84+ labels .BundleReferenceKey : helmRelease .Labels [labels .BundleReferenceKey ],
85+ })
86+ rev .Name = fmt .Sprintf ("%s-1" , ext .Name )
87+ rev .Spec .Revision = 1
88+ return rev , nil
89+ }
90+
91+ func (r * SimpleRevisionGenerator ) GenerateRevision (
92+ bundleFS fs.FS , ext * ocv1.ClusterExtension ,
93+ objectLabels , revisionAnnotations map [string ]string ,
94+ ) (* ocv1.ClusterExtensionRevision , error ) {
4495 // extract plain manifests
4596 plain , err := r .BundleRenderer .Render (bundleFS , ext )
4697 if err != nil {
@@ -50,14 +101,12 @@ func (r *SimpleRevisionGenerator) GenerateRevision(bundleFS fs.FS, ext *ocv1.Clu
50101 // objectLabels
51102 objs := make ([]ocv1.ClusterExtensionRevisionObject , 0 , len (plain ))
52103 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 )
104+ labels := maps .Clone (obj .GetLabels ())
105+ if labels == nil {
106+ labels = map [string ]string {}
60107 }
108+ maps .Copy (labels , objectLabels )
109+ obj .SetLabels (labels )
61110
62111 gvk , err := apiutil .GVKForObject (obj , r .Scheme )
63112 if err != nil {
@@ -80,18 +129,73 @@ func (r *SimpleRevisionGenerator) GenerateRevision(bundleFS fs.FS, ext *ocv1.Clu
80129 revisionAnnotations = map [string ]string {}
81130 }
82131
83- // Build desired revision
132+ return r .buildClusterExtensionRevision (objs , ext , revisionAnnotations ), nil
133+ }
134+
135+ func (r * SimpleRevisionGenerator ) buildClusterExtensionRevision (
136+ objects []ocv1.ClusterExtensionRevisionObject ,
137+ ext * ocv1.ClusterExtension ,
138+ annotations map [string ]string ,
139+ ) * ocv1.ClusterExtensionRevision {
84140 return & ocv1.ClusterExtensionRevision {
85141 ObjectMeta : metav1.ObjectMeta {
86- Annotations : revisionAnnotations ,
142+ Annotations : annotations ,
87143 Labels : map [string ]string {
88144 controllers .ClusterExtensionRevisionOwnerLabel : ext .Name ,
89145 },
90146 },
91147 Spec : ocv1.ClusterExtensionRevisionSpec {
92- Phases : PhaseSort (objs ),
148+ Phases : PhaseSort (objects ),
93149 },
94- }, nil
150+ }
151+ }
152+
153+ type BoxcutterStorageMigrator struct {
154+ ActionClientGetter helmclient.ActionClientGetter
155+ RevisionGenerator ClusterExtensionRevisionGenerator
156+ Client boxcutterStorageMigratorClient
157+ }
158+
159+ type boxcutterStorageMigratorClient interface {
160+ List (ctx context.Context , list client.ObjectList , opts ... client.ListOption ) error
161+ Create (ctx context.Context , obj client.Object , opts ... client.CreateOption ) error
162+ }
163+
164+ func (m * BoxcutterStorageMigrator ) Migrate (ctx context.Context , ext * ocv1.ClusterExtension , objectLabels map [string ]string ) error {
165+ existingRevisionList := ocv1.ClusterExtensionRevisionList {}
166+ if err := m .Client .List (ctx , & existingRevisionList , client.MatchingLabels {
167+ controllers .ClusterExtensionRevisionOwnerLabel : ext .Name ,
168+ }); err != nil {
169+ return fmt .Errorf ("listing ClusterExtensionRevisions before attempting migration: %w" , err )
170+ }
171+ if len (existingRevisionList .Items ) != 0 {
172+ // No migration needed.
173+ return nil
174+ }
175+
176+ ac , err := m .ActionClientGetter .ActionClientFor (ctx , ext )
177+ if err != nil {
178+ return err
179+ }
180+
181+ helmRelease , err := ac .Get (ext .GetName ())
182+ if errors .Is (err , driver .ErrReleaseNotFound ) {
183+ // no Helm Release -> no prior installation.
184+ return nil
185+ }
186+ if err != nil {
187+ return err
188+ }
189+
190+ rev , err := m .RevisionGenerator .GenerateRevisionFromHelmRelease (helmRelease , ext , objectLabels )
191+ if err != nil {
192+ return err
193+ }
194+
195+ if err := m .Client .Create (ctx , rev ); err != nil {
196+ return err
197+ }
198+ return nil
95199}
96200
97201type Boxcutter struct {
@@ -288,3 +392,16 @@ func (r *RegistryV1BundleRenderer) Render(bundleFS fs.FS, ext *ocv1.ClusterExten
288392 }
289393 return r .BundleRenderer .Render (reg , ext .Spec .Namespace , render .WithTargetNamespaces (watchNamespace ), render .WithCertificateProvider (r .CertificateProvider ))
290394}
395+
396+ func splitManifestDocuments (file string ) []string {
397+ //nolint:prealloc
398+ var docs []string
399+ for _ , manifest := range strings .Split (file , "\n " ) {
400+ manifest = strings .TrimSpace (manifest )
401+ if len (manifest ) == 0 {
402+ continue
403+ }
404+ docs = append (docs , manifest )
405+ }
406+ return docs
407+ }
0 commit comments