Skip to content
Merged
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
42 changes: 42 additions & 0 deletions api/v1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1

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

Expand All @@ -25,6 +26,8 @@ var ClusterExtensionKind = "ClusterExtension"
type (
UpgradeConstraintPolicy string
CRDUpgradeSafetyEnforcement string

ClusterExtensionConfigType string
)

const (
Expand All @@ -39,6 +42,8 @@ const (
// Use with caution as this can lead to unknown and potentially
// disastrous results such as data loss.
UpgradeConstraintPolicySelfCertified UpgradeConstraintPolicy = "SelfCertified"

ClusterExtensionConfigTypeInline ClusterExtensionConfigType = "Inline"
)

// ClusterExtensionSpec defines the desired state of ClusterExtension
Expand Down Expand Up @@ -92,6 +97,15 @@ type ClusterExtensionSpec struct {
//
// +optional
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`

// config contains optional configuration values applied during rendering of the
// ClusterExtension's manifests. Values can be specified inline.
//
// config is optional. When not specified, the default configuration of the resolved bundle will be used.
//
// <opcon:experimental>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at that experimental tag...

// +optional
Config *ClusterExtensionConfig `json:"config,omitempty"`
}

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

// ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into
// the ClusterExtension's rendered manifests.
//
// +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"
// +union
type ClusterExtensionConfig struct {
// configType is a required reference to the type of configuration source.
//
// Allowed values are "Inline"
//
// When this field is set to "Inline", the cluster extension configuration is defined inline within the
// ClusterExtension resource.
//
// +unionDiscriminator
// +kubebuilder:validation:Enum:="Inline"
// +kubebuilder:validation:Required
ConfigType ClusterExtensionConfigType `json:"configType"`

// inline contains JSON or YAML values specified directly in the
// ClusterExtension.
//
// inline must be set if configType is 'Inline'.
//
// +kubebuilder:validation:Type=object
// +optional
Inline *apiextensionsv1.JSON `json:"inline,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
24 changes: 24 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.

Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ spec:
description: spec is an optional field that defines the desired state
of the ClusterExtension.
properties:
config:
description: |-
config contains optional configuration values applied during rendering of the
ClusterExtension's manifests. Values can be specified inline.

config is optional. When not specified, the default configuration of the resolved bundle will be used.
properties:
configType:
description: |-
configType is a required reference to the type of configuration source.

Allowed values are "Inline"

When this field is set to "Inline", the cluster extension configuration is defined inline within the
ClusterExtension resource.
enum:
- Inline
type: string
inline:
description: |-
inline contains JSON or YAML values specified directly in the
ClusterExtension.

inline must be set if configType is 'Inline'.
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- configType
type: object
x-kubernetes-validations:
- message: inline is required when configType is Inline, and forbidden
otherwise
rule: 'has(self.configType) && self.configType == ''Inline'' ?has(self.inline)
: !has(self.inline)'
install:
description: |-
install is an optional field used to configure the installation options
Expand Down
35 changes: 35 additions & 0 deletions docs/api-reference/olmv1-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,40 @@ _Appears in:_
| `status` _[ClusterExtensionStatus](#clusterextensionstatus)_ | status is an optional field that defines the observed state of the ClusterExtension. | | |


#### ClusterExtensionConfig



ClusterExtensionConfig is a discriminated union which selects the source configuration values to be merged into
the ClusterExtension's rendered manifests.



_Appears in:_
- [ClusterExtensionSpec](#clusterextensionspec)

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `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 /> |
| `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 /> |


#### ClusterExtensionConfigType

_Underlying type:_ _string_





_Appears in:_
- [ClusterExtensionConfig](#clusterextensionconfig)

| Field | Description |
| --- | --- |
| `Inline` | |


#### ClusterExtensionInstallConfig


Expand Down Expand Up @@ -309,6 +343,7 @@ _Appears in:_
| `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 /> |
| `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 /> |
| `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. | | |
| `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> | | |


#### ClusterExtensionStatus
Expand Down
16 changes: 10 additions & 6 deletions docs/draft/howto/single-ownnamespace-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ kubectl rollout status -n olmv1-system deployment/operator-controller-controller
## Configuring the `ClusterExtension`

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

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

### Examples

Expand All @@ -70,12 +70,13 @@ apiVersion: olm.operatorframework.io/v1
kind: ClusterExtension
metadata:
name: argocd
annotations:
olm.operatorframework.io/watch-namespace: argocd-watch
spec:
namespace: argocd
serviceAccount:
name: argocd-installer
config:
inline:
watchNamespace: argocd-watch
source:
sourceType: Catalog
catalog:
Expand All @@ -96,6 +97,9 @@ spec:
namespace: argocd
serviceAccount:
name: argocd-installer
config:
inline:
watchNamespace: argocd
source:
sourceType: Catalog
catalog:
Expand Down
11 changes: 9 additions & 2 deletions internal/operator-controller/applier/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package applier_test
import (
"context"
"errors"
"fmt"
"io"
"os"
"testing"
Expand All @@ -16,6 +17,7 @@ import (
"helm.sh/helm/v3/pkg/storage/driver"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -567,8 +569,13 @@ func TestApply_InstallationWithSingleOwnNamespaceInstallSupportEnabled(t *testin
testExt := &ocv1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "testExt",
Annotations: map[string]string{
applier.AnnotationClusterExtensionWatchNamespace: expectedWatchNamespace,
},
Spec: ocv1.ClusterExtensionSpec{
Config: &ocv1.ClusterExtensionConfig{
ConfigType: ocv1.ClusterExtensionConfigTypeInline,
Inline: &apiextensionsv1.JSON{
Raw: []byte(fmt.Sprintf(`{"watchNamespace":"%s"}`, expectedWatchNamespace)),
},
},
},
}
Expand Down
32 changes: 23 additions & 9 deletions internal/operator-controller/applier/watchnamespace.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package applier

import (
"encoding/json"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"

ocv1 "github.com/operator-framework/operator-controller/api/v1"
Expand All @@ -19,14 +19,28 @@ const (
// for registry+v1 bundles. This will go away once the ClusterExtension API is updated to include
// (opaque) runtime configuration.
func GetWatchNamespace(ext *ocv1.ClusterExtension) (string, error) {
if features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) {
if ext != nil && ext.Annotations[AnnotationClusterExtensionWatchNamespace] != "" {
watchNamespace := ext.Annotations[AnnotationClusterExtensionWatchNamespace]
if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 {
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)
}
return ext.Annotations[AnnotationClusterExtensionWatchNamespace], nil
if !features.OperatorControllerFeatureGate.Enabled(features.SingleOwnNamespaceInstallSupport) {
return "", nil
}

var watchNamespace string
if ext.Spec.Config != nil && ext.Spec.Config.Inline != nil {
cfg := struct {
WatchNamespace string `json:"watchNamespace"`
}{}
if err := json.Unmarshal(ext.Spec.Config.Inline.Raw, &cfg); err != nil {
return "", fmt.Errorf("invalid bundle configuration: %w", err)
}
watchNamespace = cfg.WatchNamespace
} else if _, ok := ext.Annotations[AnnotationClusterExtensionWatchNamespace]; ok {
watchNamespace = ext.Annotations[AnnotationClusterExtensionWatchNamespace]
} else {
return "", nil
}
return corev1.NamespaceAll, nil

if errs := validation.IsDNS1123Subdomain(watchNamespace); len(errs) > 0 {
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)
}

return watchNamespace, nil
}
Loading
Loading