Skip to content

Commit cb7ab21

Browse files
committed
(rukpak) extend bundle renderer to accept config opts
Introduce BundleConfig that contains InstallConfig and DeploymentConfig.
1 parent dff07d5 commit cb7ab21

File tree

4 files changed

+342
-3
lines changed

4 files changed

+342
-3
lines changed

internal/operator-controller/rukpak/convert/helm.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ type BundleToHelmChartConverter struct {
1818
}
1919

2020
func (r *BundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, installNamespace string, watchNamespace string) (*chart.Chart, error) {
21+
// Create bundle config from watchNamespace for backward compatibility
22+
bundleConfig := &render.BundleConfig{}
23+
if watchNamespace != "" {
24+
bundleConfig.Install = &render.InstallConfig{
25+
WatchNamespace: watchNamespace,
26+
}
27+
}
28+
return r.ToHelmChartWithConfig(bundle, installNamespace, bundleConfig)
29+
}
30+
31+
func (r *BundleToHelmChartConverter) ToHelmChartWithConfig(bundle source.BundleSource, installNamespace string, config *render.BundleConfig) (*chart.Chart, error) {
2132
rv1, err := bundle.GetBundle()
2233
if err != nil {
2334
return nil, err
@@ -41,7 +52,7 @@ func (r *BundleToHelmChartConverter) ToHelmChart(bundle source.BundleSource, ins
4152

4253
objs, err := r.BundleRenderer.Render(
4354
rv1, installNamespace,
44-
render.WithTargetNamespaces(watchNamespace),
55+
render.WithBundleConfig(config),
4556
render.WithCertificateProvider(r.CertificateProvider),
4657
)
4758

internal/operator-controller/rukpak/convert/helm_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,71 @@ func Test_BundleToHelmChartConverter_ToHelmChart_Success(t *testing.T) {
192192
t.Log("Check Chart templates have the same number of resources generated by the renderer")
193193
require.Len(t, chart.Templates, 1)
194194
}
195+
196+
func Test_BundleToHelmChartConverter_ToHelmChartWithConfig_Success(t *testing.T) {
197+
converter := convert.BundleToHelmChartConverter{
198+
BundleRenderer: render.BundleRenderer{
199+
ResourceGenerators: []render.ResourceGenerator{
200+
func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) {
201+
// Verify that bundle config is passed correctly
202+
require.NotNil(t, opts.BundleConfig)
203+
require.NotNil(t, opts.BundleConfig.Install)
204+
require.Equal(t, "test-watch-namespace", opts.BundleConfig.Install.WatchNamespace)
205+
return []client.Object{&corev1.Service{}}, nil
206+
},
207+
},
208+
},
209+
}
210+
211+
b := source.FromBundle(
212+
bundle.RegistryV1{
213+
CSV: MakeCSV(
214+
WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace),
215+
WithAnnotations(map[string]string{"foo": "bar"}),
216+
),
217+
},
218+
)
219+
220+
config := &render.BundleConfig{
221+
Install: &render.InstallConfig{
222+
WatchNamespace: "test-watch-namespace",
223+
},
224+
}
225+
226+
chart, err := converter.ToHelmChartWithConfig(b, "install-namespace", config)
227+
require.NoError(t, err)
228+
require.NotNil(t, chart)
229+
require.NotNil(t, chart.Metadata)
230+
231+
t.Log("Check Chart metadata contains CSV annotations")
232+
require.Equal(t, map[string]string{"foo": "bar"}, chart.Metadata.Annotations)
233+
234+
t.Log("Check Chart templates have the same number of resources generated by the renderer")
235+
require.Len(t, chart.Templates, 1)
236+
}
237+
238+
func Test_BundleToHelmChartConverter_ToHelmChart_BackwardCompatibility(t *testing.T) {
239+
converter := convert.BundleToHelmChartConverter{
240+
BundleRenderer: render.BundleRenderer{
241+
ResourceGenerators: []render.ResourceGenerator{
242+
func(rv1 *bundle.RegistryV1, opts render.Options) ([]client.Object, error) {
243+
// Verify that watchNamespace is converted to bundle config
244+
require.NotNil(t, opts.BundleConfig)
245+
require.NotNil(t, opts.BundleConfig.Install)
246+
require.Equal(t, "test-watch-namespace", opts.BundleConfig.Install.WatchNamespace)
247+
return []client.Object{&corev1.Service{}}, nil
248+
},
249+
},
250+
},
251+
}
252+
253+
b := source.FromBundle(
254+
bundle.RegistryV1{
255+
CSV: MakeCSV(WithInstallModeSupportFor(v1alpha1.InstallModeTypeSingleNamespace)),
256+
},
257+
)
258+
259+
chart, err := converter.ToHelmChart(b, "install-namespace", "test-watch-namespace")
260+
require.NoError(t, err)
261+
require.NotNil(t, chart)
262+
}

internal/operator-controller/rukpak/render/render.go

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,26 @@ func (r ResourceGenerators) ResourceGenerator() ResourceGenerator {
5656

5757
type UniqueNameGenerator func(string, interface{}) (string, error)
5858

59+
// BundleConfig represents configuration options for bundle rendering
60+
type BundleConfig struct {
61+
// Install contains runtime configuration
62+
Install *InstallConfig `json:"install,omitempty"`
63+
// DeploymentConfig contains Deployment specific configuration
64+
DeploymentConfig map[string]interface{} `json:"deploymentConfig,omitempty"`
65+
}
66+
67+
// InstallConfig contains runtime configuration options
68+
type InstallConfig struct {
69+
// WatchNamespace specifies the namespace to watch for Single/OwnNamespace install modes
70+
WatchNamespace string `json:"watchNamespace,omitempty"`
71+
}
72+
5973
type Options struct {
6074
InstallNamespace string
6175
TargetNamespaces []string
6276
UniqueNameGenerator UniqueNameGenerator
6377
CertificateProvider CertificateProvider
78+
BundleConfig *BundleConfig
6479
}
6580

6681
func (o *Options) apply(opts ...Option) *Options {
@@ -83,6 +98,14 @@ func (o *Options) validate(rv1 *bundle.RegistryV1) (*Options, []error) {
8398
if err := validateTargetNamespaces(rv1, o.InstallNamespace, o.TargetNamespaces); err != nil {
8499
errs = append(errs, fmt.Errorf("invalid target namespaces %v: %w", o.TargetNamespaces, err))
85100
}
101+
102+
// Validate bundle configuration
103+
if o.BundleConfig != nil {
104+
if configErrs := validateBundleConfig(rv1, o.InstallNamespace, o.BundleConfig); len(configErrs) > 0 {
105+
errs = append(errs, configErrs...)
106+
}
107+
}
108+
86109
return o, errs
87110
}
88111

@@ -106,6 +129,12 @@ func WithCertificateProvider(provider CertificateProvider) Option {
106129
}
107130
}
108131

132+
func WithBundleConfig(config *BundleConfig) Option {
133+
return func(o *Options) {
134+
o.BundleConfig = config
135+
}
136+
}
137+
109138
type BundleRenderer struct {
110139
BundleValidator BundleValidator
111140
ResourceGenerators []ResourceGenerator
@@ -118,13 +147,19 @@ func (r BundleRenderer) Render(rv1 bundle.RegistryV1, installNamespace string, o
118147
}
119148

120149
// generate bundle objects
121-
genOpts, errs := (&Options{
150+
genOpts := (&Options{
122151
// default options
123152
InstallNamespace: installNamespace,
124153
TargetNamespaces: []string{metav1.NamespaceAll},
125154
UniqueNameGenerator: DefaultUniqueNameGenerator,
126155
CertificateProvider: nil,
127-
}).apply(opts...).validate(&rv1)
156+
}).apply(opts...)
157+
158+
// If bundle config is provided and contains watchNamespace, derive target namespaces from it
159+
if genOpts.BundleConfig != nil && genOpts.BundleConfig.Install != nil && genOpts.BundleConfig.Install.WatchNamespace != "" {
160+
genOpts.TargetNamespaces = []string{genOpts.BundleConfig.Install.WatchNamespace}
161+
}
162+
genOpts, errs := genOpts.validate(&rv1)
128163

129164
if len(errs) > 0 {
130165
return nil, fmt.Errorf("invalid option(s): %w", errors.Join(errs...))
@@ -175,3 +210,59 @@ func validateTargetNamespaces(rv1 *bundle.RegistryV1, installNamespace string, t
175210
}
176211
return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[string](supportedInstallModes), targetNamespaces)
177212
}
213+
214+
// validateBundleConfig validates the bundle configuration against the bundle's supported install modes
215+
func validateBundleConfig(rv1 *bundle.RegistryV1, installNamespace string, config *BundleConfig) []error {
216+
var errs []error
217+
218+
supportedInstallModes := sets.New[string]()
219+
for _, im := range rv1.CSV.Spec.InstallModes {
220+
if im.Supported {
221+
supportedInstallModes.Insert(string(im.Type))
222+
}
223+
}
224+
225+
if config.Install != nil {
226+
watchNamespace := config.Install.WatchNamespace
227+
228+
allSupported := supportedInstallModes.Has(string(v1alpha1.InstallModeTypeAllNamespaces))
229+
singleSupported := supportedInstallModes.Has(string(v1alpha1.InstallModeTypeSingleNamespace))
230+
ownSupported := supportedInstallModes.Has(string(v1alpha1.InstallModeTypeOwnNamespace))
231+
232+
switch {
233+
case allSupported && !singleSupported && !ownSupported:
234+
// All, no Single, no Own: watchNamespace should be unknown/non-existent
235+
if watchNamespace != "" {
236+
errs = append(errs, fmt.Errorf("watchNamespace configuration parameter is not supported for bundles that only support AllNamespaces install mode"))
237+
}
238+
case allSupported && singleSupported && !ownSupported:
239+
// All, Single, no Own: watchNamespace is optional
240+
// No validation needed - any value is acceptable
241+
case allSupported && !singleSupported && ownSupported:
242+
// All, no Single, Own: watchNamespace is optional (must == install namespace when set)
243+
if watchNamespace != "" && watchNamespace != installNamespace {
244+
errs = append(errs, fmt.Errorf("watchNamespace must equal install namespace (%s) when set for bundles supporting AllNamespaces and OwnNamespace install modes", installNamespace))
245+
}
246+
case !allSupported && singleSupported && !ownSupported:
247+
// no All, Single, no Own: watchNamespace is required
248+
if watchNamespace == "" {
249+
errs = append(errs, fmt.Errorf("watchNamespace configuration parameter is required for bundles that only support SingleNamespace install mode"))
250+
}
251+
case !allSupported && singleSupported && ownSupported:
252+
// no All, Single, Own: watchNamespace is required
253+
if watchNamespace == "" {
254+
errs = append(errs, fmt.Errorf("watchNamespace configuration parameter is required for bundles supporting SingleNamespace and OwnNamespace install modes"))
255+
}
256+
case !allSupported && !singleSupported && ownSupported:
257+
// no All, no Single, Own: watchNamespace should be unknown/non-existent
258+
if watchNamespace != "" {
259+
errs = append(errs, fmt.Errorf("watchNamespace configuration parameter is not supported for bundles that only support OwnNamespace install mode"))
260+
}
261+
case !allSupported && !singleSupported && !ownSupported:
262+
// no All, no Single, no Own: invalid bundle
263+
errs = append(errs, fmt.Errorf("invalid bundle: no supported install modes"))
264+
}
265+
}
266+
267+
return errs
268+
}

0 commit comments

Comments
 (0)