Skip to content

Commit d95f426

Browse files
perdasilvaPer Goncalves da Silva
andauthored
✨ ClusterExtensionConfig API (#2163)
* Extend ClusterExtension API with .spec.config Signed-off-by: Per Goncalves da Silva <[email protected]> * Generate manifests Signed-off-by: Per Goncalves da Silva <[email protected]> * Update docs Signed-off-by: Per Goncalves da Silva <[email protected]> * Update applier to take watchNamespace configuration from the extension Signed-off-by: Per Goncalves da Silva <[email protected]> * Add e2e test Signed-off-by: Per Goncalves da Silva <[email protected]> --------- Signed-off-by: Per Goncalves da Silva <[email protected]> Co-authored-by: Per Goncalves da Silva <[email protected]>
1 parent 97fabe5 commit d95f426

File tree

12 files changed

+498
-41
lines changed

12 files changed

+498
-41
lines changed

api/v1/clusterextension_types.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1
1818

1919
import (
20+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
)
2223

@@ -25,6 +26,8 @@ var ClusterExtensionKind = "ClusterExtension"
2526
type (
2627
UpgradeConstraintPolicy string
2728
CRDUpgradeSafetyEnforcement string
29+
30+
ClusterExtensionConfigType string
2831
)
2932

3033
const (
@@ -39,6 +42,8 @@ const (
3942
// Use with caution as this can lead to unknown and potentially
4043
// disastrous results such as data loss.
4144
UpgradeConstraintPolicySelfCertified UpgradeConstraintPolicy = "SelfCertified"
45+
46+
ClusterExtensionConfigTypeInline ClusterExtensionConfigType = "Inline"
4247
)
4348

4449
// ClusterExtensionSpec defines the desired state of ClusterExtension
@@ -92,6 +97,15 @@ type ClusterExtensionSpec struct {
9297
//
9398
// +optional
9499
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`
100+
101+
// config contains optional configuration values applied during rendering of the
102+
// ClusterExtension's manifests. Values can be specified inline.
103+
//
104+
// config is optional. When not specified, the default configuration of the resolved bundle will be used.
105+
//
106+
// <opcon:experimental>
107+
// +optional
108+
Config *ClusterExtensionConfig `json:"config,omitempty"`
95109
}
96110

97111
const SourceTypeCatalog = "Catalog"
@@ -138,6 +152,34 @@ type ClusterExtensionInstallConfig struct {
138152
Preflight *PreflightConfig `json:"preflight,omitempty"`
139153
}
140154

155+
// ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into
156+
// the ClusterExtension's rendered manifests.
157+
//
158+
// +kubebuilder:validation:XValidation:rule="has(self.configType) && self.configType == 'Inline' ?has(self.inline) : !has(self.inline)",message="inline is required when configType is Inline, and forbidden otherwise"
159+
// +union
160+
type ClusterExtensionConfig struct {
161+
// configType is a required reference to the type of configuration source.
162+
//
163+
// Allowed values are "Inline"
164+
//
165+
// When this field is set to "Inline", the cluster extension configuration is defined inline within the
166+
// ClusterExtension resource.
167+
//
168+
// +unionDiscriminator
169+
// +kubebuilder:validation:Enum:="Inline"
170+
// +kubebuilder:validation:Required
171+
ConfigType ClusterExtensionConfigType `json:"configType"`
172+
173+
// inline contains JSON or YAML values specified directly in the
174+
// ClusterExtension.
175+
//
176+
// inline must be set if configType is 'Inline'.
177+
//
178+
// +kubebuilder:validation:Type=object
179+
// +optional
180+
Inline *apiextensionsv1.JSON `json:"inline,omitempty"`
181+
}
182+
141183
// CatalogFilter defines the attributes used to identify and filter content from a catalog.
142184
type CatalogFilter struct {
143185
// packageName is a reference to the name of the package to be installed

api/v1/zz_generated.deepcopy.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensions.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,40 @@ spec:
5757
description: spec is an optional field that defines the desired state
5858
of the ClusterExtension.
5959
properties:
60+
config:
61+
description: |-
62+
config contains optional configuration values applied during rendering of the
63+
ClusterExtension's manifests. Values can be specified inline.
64+
65+
config is optional. When not specified, the default configuration of the resolved bundle will be used.
66+
properties:
67+
configType:
68+
description: |-
69+
configType is a required reference to the type of configuration source.
70+
71+
Allowed values are "Inline"
72+
73+
When this field is set to "Inline", the cluster extension configuration is defined inline within the
74+
ClusterExtension resource.
75+
enum:
76+
- Inline
77+
type: string
78+
inline:
79+
description: |-
80+
inline contains JSON or YAML values specified directly in the
81+
ClusterExtension.
82+
83+
inline must be set if configType is 'Inline'.
84+
type: object
85+
x-kubernetes-preserve-unknown-fields: true
86+
required:
87+
- configType
88+
type: object
89+
x-kubernetes-validations:
90+
- message: inline is required when configType is Inline, and forbidden
91+
otherwise
92+
rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline)
93+
: !has(self.inline)'
6094
install:
6195
description: |-
6296
install is an optional field used to configure the installation options

docs/api-reference/olmv1-api-reference.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,40 @@ _Appears in:_
239239
| `status` _[ClusterExtensionStatus](#clusterextensionstatus)_ | status is an optional field that defines the observed state of the ClusterExtension. | | |
240240

241241

242+
#### ClusterExtensionConfig
243+
244+
245+
246+
ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into
247+
the ClusterExtension's rendered manifests.
248+
249+
250+
251+
_Appears in:_
252+
- [ClusterExtensionSpec](#clusterextensionspec)
253+
254+
| Field | Description | Default | Validation |
255+
| --- | --- | --- | --- |
256+
| `configType` _[ClusterExtensionConfigType](#clusterextensionconfigtype)_ | configType is a required reference to the type of configuration source.<br /><br />Allowed values are "Inline"<br /><br />When this field is set to "Inline", the cluster extension configuration is defined inline within the<br />ClusterExtension resource. | | Enum: [Inline] <br />Required: \{\} <br /> |
257+
| `inline` _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#json-v1-apiextensions-k8s-io)_ | inline contains JSON or YAML values specified directly in the<br />ClusterExtension.<br /><br />inline must be set if configType is 'Inline'. | | Type: object <br /> |
258+
259+
260+
#### ClusterExtensionConfigType
261+
262+
_Underlying type:_ _string_
263+
264+
265+
266+
267+
268+
_Appears in:_
269+
- [ClusterExtensionConfig](#clusterextensionconfig)
270+
271+
| Field | Description |
272+
| --- | --- |
273+
| `Inline` | |
274+
275+
242276
#### ClusterExtensionInstallConfig
243277

244278

@@ -309,6 +343,7 @@ _Appears in:_
309343
| `serviceAccount` _[ServiceAccountReference](#serviceaccountreference)_ | serviceAccount is a reference to a ServiceAccount used to perform all interactions<br />with the cluster that are required to manage the extension.<br />The ServiceAccount must be configured with the necessary permissions to perform these interactions.<br />The ServiceAccount must exist in the namespace referenced in the spec.<br />serviceAccount is required. | | Required: \{\} <br /> |
310344
| `source` _[SourceConfig](#sourceconfig)_ | source is a required field which selects the installation source of content<br />for this ClusterExtension. Selection is performed by setting the sourceType.<br /><br />Catalog is currently the only implemented sourceType, and setting the<br />sourcetype to "Catalog" requires the catalog field to also be defined.<br /><br />Below is a minimal example of a source definition (in yaml):<br /><br />source:<br /> sourceType: Catalog<br /> catalog:<br /> packageName: example-package | | Required: \{\} <br /> |
311345
| `install` _[ClusterExtensionInstallConfig](#clusterextensioninstallconfig)_ | install is an optional field used to configure the installation options<br />for the ClusterExtension such as the pre-flight check configuration. | | |
346+
| `config` _[ClusterExtensionConfig](#clusterextensionconfig)_ | config contains optional configuration values applied during rendering of the<br />ClusterExtension's manifests. Values can be specified inline.<br /><br />config is optional. When not specified, the default configuration of the resolved bundle will be used.<br /><br /><opcon:experimental> | | |
312347

313348

314349
#### ClusterExtensionStatus

docs/draft/howto/single-ownnamespace-install.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ kubectl rollout status -n olmv1-system deployment/operator-controller-controller
5656
## Configuring the `ClusterExtension`
5757

5858
A `ClusterExtension` can be configured to install bundle in `Single-` or `OwnNamespace` mode through the
59-
`olm.operatorframework.io/watch-namespace: <namespace>` annotation. The *installMode* is inferred in the following way:
59+
`.spec.config.inline.watchNamespace` property. The *installMode* is inferred in the following way:
6060

61-
- *AllNamespaces*: `<namespace>` is empty, or the annotation is not present
62-
- *OwnNamespace*: `<namespace>` is the install namespace (i.e. `.spec.namespace`)
63-
- *SingleNamespace*: `<namespace>` not the install namespace
61+
- *AllNamespaces*: `watchNamespace` is empty, or not set
62+
- *OwnNamespace*: `watchNamespace` is the install namespace (i.e. `.spec.namespace`)
63+
- *SingleNamespace*: `watchNamespace` *not* the install namespace
6464

6565
### Examples
6666

@@ -70,12 +70,13 @@ apiVersion: olm.operatorframework.io/v1
7070
kind: ClusterExtension
7171
metadata:
7272
name: argocd
73-
annotations:
74-
olm.operatorframework.io/watch-namespace: argocd-watch
7573
spec:
7674
namespace: argocd
7775
serviceAccount:
7876
name: argocd-installer
77+
config:
78+
inline:
79+
watchNamespace: argocd-watch
7980
source:
8081
sourceType: Catalog
8182
catalog:
@@ -96,6 +97,9 @@ spec:
9697
namespace: argocd
9798
serviceAccount:
9899
name: argocd-installer
100+
config:
101+
inline:
102+
watchNamespace: argocd
99103
source:
100104
sourceType: Catalog
101105
catalog:

internal/operator-controller/applier/helm_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package applier_test
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"io"
78
"os"
89
"testing"
@@ -16,6 +17,7 @@ import (
1617
"helm.sh/helm/v3/pkg/storage/driver"
1718
corev1 "k8s.io/api/core/v1"
1819
rbacv1 "k8s.io/api/rbac/v1"
20+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1921
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022
featuregatetesting "k8s.io/component-base/featuregate/testing"
2123
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -567,8 +569,13 @@ func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testin
567569
testExt := &ocv1.ClusterExtension{
568570
ObjectMeta: metav1.ObjectMeta{
569571
Name: "testExt",
570-
Annotations: map[string]string{
571-
applier.AnnotationClusterExtensionWatchNamespace: expectedWatchNamespace,
572+
},
573+
Spec: ocv1.ClusterExtensionSpec{
574+
Config: &ocv1.ClusterExtensionConfig{
575+
ConfigType: ocv1.ClusterExtensionConfigTypeInline,
576+
Inline: &apiextensionsv1.JSON{
577+
Raw: []byte(fmt.Sprintf(`{"watchNamespace":"%s"}`, expectedWatchNamespace)),
578+
},
572579
},
573580
},
574581
}
Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package applier
22

33
import (
4+
"encoding/json"
45
"fmt"
56

6-
corev1 "k8s.io/api/core/v1"
77
"k8s.io/apimachinery/pkg/util/validation"
88

99
ocv1 "github.com/operator-framework/operator-controller/api/v1"
@@ -19,14 +19,28 @@ const (
1919
// for registry+v1 bundles. This will go away once the ClusterExtension API is updated to include
2020
// (opaque) runtime configuration.
2121
func GetWatchNamespace(ext *ocv1.ClusterExtension) (string, error) {
22-
if features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) {
23-
if ext != nil && ext.Annotations[AnnotationClusterExtensionWatchNamespace] != "" {
24-
watchNamespace := ext.Annotations[AnnotationClusterExtensionWatchNamespace]
25-
if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 {
26-
return "", fmt.Errorf("invalid watch namespace '%s': namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", watchNamespace)
27-
}
28-
return ext.Annotations[AnnotationClusterExtensionWatchNamespace], nil
22+
if !features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) {
23+
return "", nil
24+
}
25+
26+
var watchNamespace string
27+
if ext.Spec.Config != nil && ext.Spec.Config.Inline != nil {
28+
cfg := struct {
29+
WatchNamespace string `json:"watchNamespace"`
30+
}{}
31+
if err := json.Unmarshal(ext.Spec.Config.Inline.Raw, &cfg); err != nil {
32+
return "", fmt.Errorf("invalid bundle configuration: %w", err)
2933
}
34+
watchNamespace = cfg.WatchNamespace
35+
} else if _, ok := ext.Annotations[AnnotationClusterExtensionWatchNamespace]; ok {
36+
watchNamespace = ext.Annotations[AnnotationClusterExtensionWatchNamespace]
37+
} else {
38+
return "", nil
3039
}
31-
return corev1.NamespaceAll, nil
40+
41+
if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 {
42+
return "", fmt.Errorf("invalid watch namespace '%s': namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", watchNamespace)
43+
}
44+
45+
return watchNamespace, nil
3246
}

0 commit comments

Comments
 (0)