Skip to content
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
27 changes: 19 additions & 8 deletions api/v1/clusterextension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,25 @@ const (

// ClusterExtensionSpec defines the desired state of ClusterExtension
type ClusterExtensionSpec struct {
// namespace specifies a Kubernetes namespace.
// It designates the default namespace where namespace-scoped resources for the extension are applied to the cluster.
// Some extensions may contain namespace-scoped resources to be applied in other namespaces.
// This namespace must exist.
// namespace is optional. When set, it specifies an existing namespace where
// namespace-scoped resources for the extension are applied. The namespace must
// already exist on the cluster. When omitted, operator-controller resolves and
// creates a managed namespace from bundle metadata.
//
// The namespace field is required, immutable, and follows the DNS label standard as defined in [RFC 1123].
// The mode (set vs omitted) is locked at creation time and cannot be changed.
// When set, the value is immutable.
//
// The namespace field follows the DNS label standard as defined in [RFC 1123].
// It must contain only lowercase alphanumeric characters or hyphens (-), start and end with an alphanumeric character,
// and be no longer than 63 characters.
//
// [RFC 1123]: https://tools.ietf.org/html/rfc1123
//
// +kubebuilder:validation:MaxLength:=63
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="namespace is immutable"
// +kubebuilder:validation:XValidation:rule="self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label"
// +required
// +kubebuilder:validation:XValidation:rule="self == '' || self.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\")",message="namespace must be a valid DNS1123 label"
// +kubebuilder:validation:XValidation:rule="oldSelf == '' || self == oldSelf",message="namespace is immutable once set"
// +kubebuilder:validation:XValidation:rule="oldSelf != '' || self == ''",message="namespace cannot be set after creation; mode is locked at creation time"
// +optional
Namespace string `json:"namespace"`

// serviceAccount is a deprecated field and is completely ignored.
Expand Down Expand Up @@ -545,6 +549,13 @@ type ClusterExtensionStatus struct {
// +optional
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`

// namespace is the resolved namespace where the extension is installed.
// For user-provided namespaces, this mirrors spec.namespace.
// For managed namespaces, this shows the name resolved from bundle metadata.
//
// +optional
Namespace string `json:"namespace,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This field only seems relevant for bundles that assume a single namespace as an install namespace. That is true for registry+v1 bundles, but is not generally true at the cluster scope.

For now, I'd suggest dropping this. The set of managed objects (including the registry+v1 install namespace will be discoverable in the ClusterObjectSet.

We can wait to see what feedback we get from real-world usage to help us decide what if anything to surface in the CE status.


// activeRevisions holds a list of currently active (non-archived) ClusterObjectSets,
// including both installed and rolling out revisions.
// +listType=map
Expand Down
13 changes: 8 additions & 5 deletions applyconfigurations/api/v1/clusterextensionspec.go

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

14 changes: 13 additions & 1 deletion applyconfigurations/api/v1/clusterextensionstatus.go

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

3 changes: 3 additions & 0 deletions applyconfigurations/internal/internal.go

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

2 changes: 2 additions & 0 deletions cmd/operator-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ func (c *boxcutterReconcilerConfigurator) Configure(ceReconciler *controllers.Cl
controllers.RetrieveRevisionStates(revisionStatesGetter),
controllers.ResolveBundle(c.resolver, c.mgr.GetClient()),
controllers.UnpackBundle(c.imagePuller, c.imageCache),
controllers.ResolveNamespace(coreClient),
controllers.ApplyBundleWithBoxcutter(appl.Apply),
}

Expand Down Expand Up @@ -746,6 +747,7 @@ func (c *helmReconcilerConfigurator) Configure(ceReconciler *controllers.Cluster
controllers.RetrieveRevisionStates(revisionStatesGetter),
controllers.ResolveBundle(c.resolver, c.mgr.GetClient()),
controllers.UnpackBundle(c.imagePuller, c.imageCache),
controllers.ResolveNamespace(coreClient),
controllers.ApplyBundle(appl),
}

Expand Down
45 changes: 45 additions & 0 deletions docs/concepts/managed-namespaces.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's move docs to the drafts folder. We have a process where @michaelryanpeter reviews our drafts and incorporated into the live docs site as a separate step.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Managed Namespaces

## What is a managed namespace?

When you create a ClusterExtension without specifying `spec.namespace`, operator-controller automatically creates and manages a namespace for the operator. The namespace name comes from the bundle's metadata or defaults to `<packageName>-system`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

True for registry+v1, but other bundles may just directly include one (or more!) namespaces. I think that's why @perdasilva was suggesting that we move this field to be in the config and deprecate it in the spec.


When you specify `spec.namespace`, the namespace must already exist on the cluster and operator-controller installs into it without managing its lifecycle.

The mode is locked at creation time: you cannot switch between managed and user-provided after the ClusterExtension is created.

## Namespace resolution

In managed mode, the namespace name is resolved from bundle CSV annotations in this order:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should call out that this is the logic for registry+v1.

And then a line that says other formats are like to have different behaviors for namespace handling (again this points more toward namespace as bundle-specific config rather than CE-level specification)


1. `operatorframework.io/suggested-namespace-template`: the `metadata.name` field from the JSON template
2. `operators.operatorframework.io/suggested-namespace`: a plain string with the preferred name
3. `<packageName>-system`: convention fallback

## What belongs in a managed namespace

- The operator's own workloads (deployments, services, configmaps)
- The operator's RBAC resources (service accounts, roles, role bindings)
- CRDs and webhooks installed by the operator

## What does NOT belong in a managed namespace

- User application workloads
- Shared services used by multiple operators
- Persistent data that should survive operator uninstallation

## Deletion behavior

Deleting a ClusterExtension with a managed namespace **deletes the entire namespace and everything in it.** If you have created resources in the managed namespace that are not part of the operator, they will be lost.

If you need the namespace to persist beyond the operator's lifecycle, use `spec.namespace` to point at an existing namespace you manage yourself.

## PSA labels

If the bundle declares PSA requirements via `operatorframework.io/suggested-namespace-template`, those labels are applied to the managed namespace automatically. This ensures the namespace has the correct Pod Security Admission level for the operator's workloads without manual configuration.

## Drift protection

Managed namespaces are reconciled by the ClusterObjectSet controller. If someone manually modifies or removes labels that the controller owns (e.g., PSA labels from the template), they are automatically restored.

Labels or annotations added by other actors that don't conflict with controller-owned fields are preserved.
58 changes: 58 additions & 0 deletions docs/howto/namespace-configuration-for-authors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Namespace Configuration for Bundle Authors

Bundle authors can specify their preferred namespace configuration through CSV annotations. These annotations are used by operator-controller when the cluster admin does not provide an explicit `spec.namespace`.

## Annotations

### `operatorframework.io/suggested-namespace-template`

Full namespace template with metadata. Use this when your operator needs specific labels or annotations on its namespace (e.g., PSA labels).

```yaml
apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
name: my-operator.v1.0.0
annotations:
operatorframework.io/suggested-namespace-template: |
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"name": "my-operator-system",
"labels": {
"pod-security.kubernetes.io/enforce": "privileged",
"pod-security.kubernetes.io/audit": "privileged",
"pod-security.kubernetes.io/warn": "privileged"
}
}
}
```

### `operators.operatorframework.io/suggested-namespace`

Simple namespace name without metadata. Use this when you want a specific name but don't need labels or annotations.

```yaml
annotations:
operators.operatorframework.io/suggested-namespace: my-operator-system
```

### No annotation

If neither annotation is present, operator-controller uses `<packageName>-system` as the namespace name.

## Priority

If both annotations are present, `suggested-namespace-template` takes priority.

## Guidelines

- Always include PSA labels if your operator runs privileged containers.
- Use a descriptive, unique namespace name that includes your package name to avoid collisions.
- Do not assume the namespace name will be exactly what you suggest as cluster admins can override it by setting `spec.namespace`.
- The namespace name from the template is used only when `spec.namespace` is omitted. When set, the admin's choice takes precedence and no namespace object is created.

## Consistency across bundle formats

The `operatorframework.io/suggested-namespace-template` and `operators.operatorframework.io/suggested-namespace` annotations are the canonical way to declare namespace preferences. Future bundle formats should use the same annotation keys to avoid divergence across the ecosystem.
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,29 @@ spec:
rule: has(self.preflight)
namespace:
description: |-
namespace specifies a Kubernetes namespace.
It designates the default namespace where namespace-scoped resources for the extension are applied to the cluster.
Some extensions may contain namespace-scoped resources to be applied in other namespaces.
This namespace must exist.
namespace is optional. When set, it specifies an existing namespace where
namespace-scoped resources for the extension are applied. The namespace must
already exist on the cluster. When omitted, operator-controller resolves and
creates a managed namespace from bundle metadata.

The namespace field is required, immutable, and follows the DNS label standard as defined in [RFC 1123].
The mode (set vs omitted) is locked at creation time and cannot be changed.
When set, the value is immutable.

The namespace field follows the DNS label standard as defined in [RFC 1123].
It must contain only lowercase alphanumeric characters or hyphens (-), start and end with an alphanumeric character,
and be no longer than 63 characters.

[RFC 1123]: https://tools.ietf.org/html/rfc1123
maxLength: 63
type: string
x-kubernetes-validations:
- message: namespace is immutable
rule: self == oldSelf
- message: namespace must be a valid DNS1123 label
rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
rule: self == '' || self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
- message: namespace is immutable once set
rule: oldSelf == '' || self == oldSelf
- message: namespace cannot be set after creation; mode is locked
at creation time
rule: oldSelf != '' || self == ''
progressDeadlineMinutes:
description: |-
progressDeadlineMinutes is an optional field that defines the maximum period
Expand Down Expand Up @@ -493,7 +499,6 @@ spec:
rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ?
has(self.catalog) : !has(self.catalog)'
required:
- namespace
- source
type: object
status:
Expand Down Expand Up @@ -725,6 +730,12 @@ spec:
required:
- bundle
type: object
namespace:
description: |-
namespace is the resolved namespace where the extension is installed.
For user-provided namespaces, this mirrors spec.namespace.
For managed namespaces, this shows the name resolved from bundle metadata.
type: string
type: object
type: object
served: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,29 @@ spec:
rule: has(self.preflight)
namespace:
description: |-
namespace specifies a Kubernetes namespace.
It designates the default namespace where namespace-scoped resources for the extension are applied to the cluster.
Some extensions may contain namespace-scoped resources to be applied in other namespaces.
This namespace must exist.
namespace is optional. When set, it specifies an existing namespace where
namespace-scoped resources for the extension are applied. The namespace must
already exist on the cluster. When omitted, operator-controller resolves and
creates a managed namespace from bundle metadata.

The namespace field is required, immutable, and follows the DNS label standard as defined in [RFC 1123].
The mode (set vs omitted) is locked at creation time and cannot be changed.
When set, the value is immutable.

The namespace field follows the DNS label standard as defined in [RFC 1123].
It must contain only lowercase alphanumeric characters or hyphens (-), start and end with an alphanumeric character,
and be no longer than 63 characters.

[RFC 1123]: https://tools.ietf.org/html/rfc1123
maxLength: 63
type: string
x-kubernetes-validations:
- message: namespace is immutable
rule: self == oldSelf
- message: namespace must be a valid DNS1123 label
rule: self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
rule: self == '' || self.matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
- message: namespace is immutable once set
rule: oldSelf == '' || self == oldSelf
- message: namespace cannot be set after creation; mode is locked
at creation time
rule: oldSelf != '' || self == ''
serviceAccount:
description: |-
serviceAccount is a deprecated field and is completely ignored.
Expand Down Expand Up @@ -445,7 +451,6 @@ spec:
rule: 'has(self.sourceType) && self.sourceType == ''Catalog'' ?
has(self.catalog) : !has(self.catalog)'
required:
- namespace
- source
type: object
status:
Expand Down Expand Up @@ -569,6 +574,12 @@ spec:
required:
- bundle
type: object
namespace:
description: |-
namespace is the resolved namespace where the extension is installed.
For user-provided namespaces, this mirrors spec.namespace.
For managed namespaces, this shows the name resolved from bundle metadata.
type: string
type: object
type: object
served: true
Expand Down
Loading
Loading