Skip to content

Configuration Support for registry+v1 bundle (w/ Helm aware) #2132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .bingo/go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files.
module _ // Fake go.mod auto-created by 'bingo' for go -moddir compatibility with non-Go projects. Commit this file, together with other .mod files.

go 1.23.6
25 changes: 25 additions & 0 deletions api/v1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1

import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -92,6 +94,13 @@ type ClusterExtensionSpec struct {
//
// +optional
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`

// config contains configuration values applied during rendering of the
// ClusterExtension's manifests. Values can be specified inline or sourced
// from a referenced Secret.
//
// +optional
Config *ClusterExtensionConfig `json:"config,omitempty"`
}

const SourceTypeCatalog = "Catalog"
Expand Down Expand Up @@ -138,6 +147,22 @@ type ClusterExtensionInstallConfig struct {
Preflight *PreflightConfig `json:"preflight,omitempty"`
}

// ClusterExtensionConfig defines configuration values to be merged into
// the ClusterExtension's rendered manifests.
type ClusterExtensionConfig struct {
// inline contains JSON or YAML values specified directly in the
// ClusterExtension.
// +optional
Inline *apiextensionsv1.JSON `json:"inline,omitempty"`

// secretRef references a key in a Secret that contains JSON or YAML
// values.
// The referenced Secret must exist in the same namespace as the
// ClusterExtension.
// +optional
SecretRef *corev1.SecretKeySelector `json:"secretRef,omitempty"`
}

// CatalogFilter defines the attributes used to identify and filter content from a catalog.
type CatalogFilter struct {
// packageName is a reference to the name of the package to be installed
Expand Down
35 changes: 35 additions & 0 deletions api/v1/clusterextensionconfig_deepcopy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package v1

import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

// DeepCopyInto copies the receiver into out. in must be non-nil.
func (in *ClusterExtensionConfig) DeepCopyInto(out *ClusterExtensionConfig) {
*out = *in
if in.Inline != nil {
out.Inline = make(map[string]apiextensionsv1.JSON, len(in.Inline))

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / extension-developer-e2e

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / extension-developer-e2e

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / experimental-e2e

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / experimental-e2e

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / e2e-kind

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / e2e-kind

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / unit-test-basic

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / unit-test-basic

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / goreleaser

invalid argument: in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) for built-in len

Check failure on line 12 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / goreleaser

cannot use make(map[string]apiextensionsv1.JSON, len(in.Inline)) (value of type map[string]"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON) as *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON value in assignment
for k, v := range in.Inline {

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / extension-developer-e2e

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / experimental-e2e

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / e2e-kind

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / unit-test-basic

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 13 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / goreleaser

cannot range over in.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)
if v.Raw != nil {
out.Inline[k] = apiextensionsv1.JSON{Raw: append([]byte(nil), v.Raw...)}

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / extension-developer-e2e

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / experimental-e2e

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / e2e-kind

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / unit-test-basic

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 15 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / goreleaser

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)
} else {
out.Inline[k] = apiextensionsv1.JSON{}

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / extension-developer-e2e

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / experimental-e2e

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / e2e-kind

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / lint

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / unit-test-basic

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)

Check failure on line 17 in api/v1/clusterextensionconfig_deepcopy.go

View workflow job for this annotation

GitHub Actions / goreleaser

invalid operation: cannot index out.Inline (variable of type *"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1".JSON)
}
}
}
if in.SecretRef != nil {
out.SecretRef = new(corev1.SecretKeySelector)
in.SecretRef.DeepCopyInto(out.SecretRef)
}
}

// DeepCopy creates a new ClusterExtensionConfig by copying the receiver.
func (in *ClusterExtensionConfig) DeepCopy() *ClusterExtensionConfig {
if in == nil {
return nil
}
out := new(ClusterExtensionConfig)
in.DeepCopyInto(out)
return out
}
28 changes: 28 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions cmd/demo-config/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package main

import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"sigs.k8s.io/yaml"

"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"

"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/registryv1"
)

func main() {
helmFlag := flag.Bool("helm", false, "render as Helm chart instead of registry+v1 bundle")
flag.Parse()
if flag.NArg() != 2 {
fmt.Fprintf(os.Stderr, "usage: %s [-helm] <bundle-dir> <install-namespace>\n", os.Args[0])
os.Exit(1)
}
bundleDir := flag.Arg(0)
installNs := flag.Arg(1)

// load optional configuration values from config.yaml
cfg := map[string]interface{}{}
configFile := filepath.Join(bundleDir, "config.yaml")
if data, err := os.ReadFile(configFile); err == nil {
if err := yaml.Unmarshal(data, &cfg); err != nil {
fmt.Fprintf(os.Stderr, "error unmarshalling config file %q: %v\n", configFile, err)
os.Exit(1)
}
}

if *helmFlag {
// ensure a Helm chart is present
// look for Chart.yaml in the specified dir, or try replacing "bundles" -> "charts" if not found
chartPath := filepath.Join(bundleDir, "Chart.yaml")
if _, err := os.Stat(chartPath); err != nil {
// fallback: swap a "bundles" segment to "charts" in the path
parts := strings.Split(bundleDir, string(os.PathSeparator))
for i, p := range parts {
if p == "bundles" {
parts[i] = "charts"
altDir := filepath.Join(parts...)
if _, err2 := os.Stat(filepath.Join(altDir, "Chart.yaml")); err2 == nil {
bundleDir = altDir
chartPath = filepath.Join(bundleDir, "Chart.yaml")
break
}
}
}
if _, err := os.Stat(chartPath); err != nil {
fmt.Fprintf(os.Stderr, "error: helm chart not found in %q: %v\n", bundleDir, err)
os.Exit(1)
}
}
// render with the Helm engine
chrt, err := loader.Load(bundleDir)
if err != nil {
fmt.Fprintf(os.Stderr, "error loading Helm chart: %v\n", err)
os.Exit(1)
}
// load values.yaml and apply any config overrides
values := map[string]interface{}{}
valuesFile := filepath.Join(bundleDir, "values.yaml")
if data, err := os.ReadFile(valuesFile); err == nil {
if err := yaml.Unmarshal(data, &values); err != nil {
fmt.Fprintf(os.Stderr, "error unmarshalling values file %q: %v\n", valuesFile, err)
os.Exit(1)
}
}
for k, v := range cfg {
values[k] = v
}
// render the chart templates using the provided values under the 'Values' key
renderContext := chartutil.Values{"Values": values}
rendered, err := engine.Engine{}.Render(chrt, renderContext)
if err != nil {
fmt.Fprintf(os.Stderr, "error rendering Helm chart: %v\n", err)
os.Exit(1)
}
for name, content := range rendered {
fmt.Printf("---\n# Source: %s\n%s\n", name, content)
}
return
}

// parse registry+v1 bundle
regv1, err := source.FromFS(os.DirFS(bundleDir)).GetBundle()
if err != nil {
fmt.Fprintf(os.Stderr, "error parsing bundle directory: %v\n", err)
os.Exit(1)
}

// render bundle with configuration
opts := []render.Option{render.WithConfig(cfg)}
objs, err := registryv1.Renderer.Render(regv1, installNs, opts...)
if err != nil {
fmt.Fprintf(os.Stderr, "error rendering bundle: %v\n", err)
os.Exit(1)
}

for _, obj := range objs {
data, err := yaml.Marshal(obj)
if err != nil {
fmt.Fprintf(os.Stderr, "error marshaling object: %v\n", err)
os.Exit(1)
}
fmt.Printf("---\n%s", string(data))
}
}
1 change: 1 addition & 0 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ func run() error {
IsWebhookSupportEnabled: isWebhookSupportEnabled,
},
PreAuthorizer: preAuth,
Client: mgr.GetClient(),
}

cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ spec:
description: spec is an optional field that defines the desired state
of the ClusterExtension.
properties:
config:
description: |-
config contains arbitrary JSON configuration values to be applied at render time.
These values will be merged into the bundle manifests during rendering.
type: object
x-kubernetes-preserve-unknown-fields: true
install:
description: |-
install is an optional field used to configure the installation options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ spec:
description: spec is an optional field that defines the desired state
of the ClusterExtension.
properties:
config:
description: |-
config contains arbitrary JSON configuration values to be applied at render time.
These values will be merged into the bundle manifests during rendering.
type: object
x-kubernetes-preserve-unknown-fields: true
install:
description: |-
install is an optional field used to configure the installation options
Expand Down
6 changes: 6 additions & 0 deletions config/samples/olm_v1_clusterextension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,9 @@ spec:
catalog:
packageName: argocd-operator
version: 0.6.0
config:
inline:
exampleKey: exampleValue
secretRef:
name: argocd-config
key: config.yaml
9 changes: 6 additions & 3 deletions docs/tutorials/install-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ For information on determining the ServiceAccount's permission, please see [Deri

## Procedure

1. Create a CR for the Kubernetes extension you want to install:
1. Create a CR for the Kubernetes extension you want to install. You can also specify arbitrary configuration values under `spec.config` (per [RFC: Registry+v1 Configuration Support](../../RFC_Config_registry+v1_bundle_config.md)):

``` yaml title="Example CR"
```yaml title="Example CR"
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
Expand All @@ -46,8 +46,11 @@ For information on determining the ServiceAccount's permission, please see [Deri
sourceType: Catalog
catalog:
packageName: <package_name>
channels: [<channel1>,<channel2]
channels: [<channel1>,<channel2>]
version: "<version>"
config:
version: "v2.0.0-demo"
name: "demo-configmap"
```

`extension_name`
Expand Down
4 changes: 4 additions & 0 deletions hack/demo/resources/own-namespace-demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ spec:
catalog:
packageName: argocd-operator
version: 0.6.0
config:
secretRef:
name: argocd-config
key: config.yaml
3 changes: 3 additions & 0 deletions hack/demo/resources/single-namespace-demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ spec:
catalog:
packageName: argocd-operator
version: 0.6.0
config:
inline:
exampleKey: exampleValue
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ spec:
catalog:
packageName: argocd-operator
version: 0.6.0
config:
inline:
exampleKey: exampleValue
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ spec:
selector: {}
upgradeConstraintPolicy: CatalogProvided
sourceType: Catalog
config:
secretRef:
name: webhook-config
key: config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1

import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -93,6 +95,26 @@ type ClusterExtensionSpec struct {
//
// +optional
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`

// config contains configuration values to be applied at render time.
// +optional
Config *ClusterExtensionConfig `json:"config,omitempty"`
}

// ClusterExtensionConfig provides configuration values to be applied at render time.
//
// <opcon:standard:validation:XValidation:rule="has(self.inline) || has(self.secretRef)",message="at least one of [inline, secretRef] is required when config is specified">
type ClusterExtensionConfig struct {
// inline contains arbitrary configuration values to be applied at render time.
// These values will be merged into the bundle manifests during rendering.
// +optional
// +kubebuilder:validation:Type=object
// +kubebuilder:pruning:PreserveUnknownFields
Inline map[string]apiextensionsv1.JSON `json:"inline,omitempty"`

// secretRef references a Secret containing configuration data.
// +optional
SecretRef *corev1.SecretKeySelector `json:"secretRef,omitempty"`
}

const SourceTypeCatalog = "Catalog"
Expand Down
Loading
Loading