Skip to content

Commit df35dcd

Browse files
perdasilvaPer Goncalves da Silva
and
Per Goncalves da Silva
authored
✨ Single/Own Namespace Install Mode Support (#1724)
* Add feature gate Signed-off-by: Per Goncalves da Silva <[email protected]> * Get extension watch namespace from annotation Signed-off-by: Per Goncalves da Silva <[email protected]> * Make bundle->chart function configurable in applier and add tests Signed-off-by: Per Goncalves da Silva <[email protected]> * Add resource dir env var to demo generation script Signed-off-by: Per Goncalves da Silva <[email protected]> * Add feature demo Signed-off-by: Per Goncalves da Silva <[email protected]> * Add docs Signed-off-by: Per Goncalves da Silva <[email protected]> * Fixup tests 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 13a594f commit df35dcd

File tree

14 files changed

+638
-114
lines changed

14 files changed

+638
-114
lines changed

cmd/operator-controller/main.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import (
6565
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
6666
"github.com/operator-framework/operator-controller/internal/operator-controller/finalizers"
6767
"github.com/operator-framework/operator-controller/internal/operator-controller/resolve"
68+
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert"
6869
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety"
6970
"github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
7071
fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs"
@@ -407,8 +408,9 @@ func run() error {
407408
}
408409

409410
helmApplier := &applier.Helm{
410-
ActionClientGetter: acg,
411-
Preflights: preflights,
411+
ActionClientGetter: acg,
412+
Preflights: preflights,
413+
BundleToHelmChartFn: convert.RegistryV1ToHelmChart,
412414
}
413415

414416
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
## Description
2+
3+
!!! note
4+
This feature is still in *alpha* the `SingleOwnNamespaceInstallSupport` feature-gate must be enabled to make use of it.
5+
See the instructions below on how to enable it.
6+
7+
---
8+
9+
A component of OLMv0's multi-tenancy feature is its support of four [*installModes*](https://olm.operatorframework.io/docs/advanced-tasks/operator-scoping-with-operatorgroups/#targetnamespaces-and-their-relationship-to-installmodes):
10+
for operator installation:
11+
12+
- *OwnNamespace*: If supported, the operator can be configured to watch for events in the namespace it is deployed in.
13+
- *SingleNamespace*: If supported, the operator can be configured to watch for events in a single namespace that the operator is not deployed in.
14+
- *MultiNamespace*: If supported, the operator can be configured to watch for events in more than one namespace.
15+
- *AllNamespaces*: If supported, the operator can be configured to watch for events in all namespaces.
16+
17+
OLMv1 will not attempt multi-tenancy (see [design decisions document](../../project/olmv1_design_decisions.md)) and will think of operators
18+
as globally installed, i.e. in OLMv0 parlance, as installed in *AllNamespaces* mode. However, there are operators that
19+
were intended only for the *SingleNamespace* and *OwnNamespace* install modes. In order to make these operators installable in v1 while they
20+
transition to the new model, v1 is adding support for these two new *installModes*. It should be noted that, in line with v1's no multi-tenancy policy,
21+
users will not be able to install the same operator multiple times, and that in future iterations of the registry bundle format will not
22+
include *installModes*.
23+
24+
## Demos
25+
26+
### SingleNamespace Install
27+
28+
[![SingleNamespace Install Demo](https://asciinema.org/a/w1IW0xWi1S9cKQFb9jnR07mgh.svg)](https://asciinema.org/a/w1IW0xWi1S9cKQFb9jnR07mgh)
29+
30+
### OwnNamespace Install
31+
32+
[![OwnNamespace Install Demo](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i.svg)](https://asciinema.org/a/Rxx6WUwAU016bXFDW74XLcM5i)
33+
34+
## Enabling the Feature-Gate
35+
36+
!!! tip
37+
38+
This guide assumes OLMv1 is already installed. If that is not the case,
39+
you can follow the [getting started](../../getting-started/olmv1_getting_started.md) guide to install OLMv1.
40+
41+
---
42+
43+
Patch the `operator-controller` `Deployment` adding `--feature-gates=SingleOwnNamespaceInstallSupport=true` to the
44+
controller container arguments:
45+
46+
```terminal title="Enable SingleOwnNamespaceInstallSupport feature-gate"
47+
kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]'
48+
```
49+
50+
Wait for `Deployment` rollout:
51+
52+
```terminal title="Wait for Deployment rollout"
53+
kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager
54+
```
55+
56+
## Configuring the `ClusterExtension`
57+
58+
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:
60+
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
64+
65+
### Examples
66+
67+
``` terminal title="SingleNamespace install mode example"
68+
kubectl apply -f - <<EOF
69+
apiVersion: olm.operatorframework.io/v1
70+
kind: ClusterExtension
71+
metadata:
72+
name: argocd
73+
annotations:
74+
olm.operatorframework.io/watch-namespace: argocd-watch
75+
spec:
76+
namespace: argocd
77+
serviceAccount:
78+
name: argocd-installer
79+
source:
80+
sourceType: Catalog
81+
catalog:
82+
packageName: argocd-operator
83+
version: 0.2.1 # Update to version 0.2.1
84+
EOF
85+
```
86+
87+
``` terminal title="OwnNamespace install mode example"
88+
kubectl apply -f - <<EOF
89+
apiVersion: olm.operatorframework.io/v1
90+
kind: ClusterExtension
91+
metadata:
92+
name: argocd
93+
annotations:
94+
olm.operatorframework.io/watch-namespace: argocd
95+
spec:
96+
namespace: argocd
97+
serviceAccount:
98+
name: argocd-installer
99+
source:
100+
sourceType: Catalog
101+
catalog:
102+
packageName: argocd-operator
103+
version: 0.2.1 # Update to version 0.2.1
104+
EOF
105+
```

hack/demo/generate-asciidemo.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
trap cleanup SIGINT SIGTERM EXIT
44

55
SCRIPTPATH="$( cd -- "$(dirname "$0")" > /dev/null 2>&1 ; pwd -P )"
6+
export DEMO_RESOURCE_DIR="${SCRIPTPATH}/resources"
67

78
check_prereq() {
89
prog=$1
@@ -80,7 +81,6 @@ for prereq in "asciinema curl"; do
8081
check_prereq ${prereq}
8182
done
8283

83-
8484
curl https://raw.githubusercontent.com/zechris/asciinema-rec_script/main/bin/asciinema-rec_script -o ${WKDIR}/asciinema-rec_script
8585
chmod +x ${WKDIR}/asciinema-rec_script
8686
screencast=${WKDIR}/${DEMO_NAME}.cast ${WKDIR}/asciinema-rec_script ${SCRIPTPATH}/${DEMO_SCRIPT}

hack/demo/own-namespace-demo.sh

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Welcome to the OwnNamespace install mode demo
5+
#
6+
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
7+
8+
# enable 'SingleOwnNamespaceInstallSupport' feature gate
9+
kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]'
10+
11+
# wait for operator-controller to become available
12+
kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager
13+
14+
# create install namespace
15+
kubectl create ns argocd-system
16+
17+
# create installer service account
18+
kubectl create serviceaccount -n argocd-system argocd-installer
19+
20+
# give installer service account admin privileges (not for production environments)
21+
kubectl create clusterrolebinding argocd-installer-crb --clusterrole=cluster-admin --serviceaccount=argocd-system:argocd-installer
22+
23+
# install cluster extension in own namespace install mode (watch-namespace == install namespace == argocd-system)
24+
cat ${DEMO_RESOURCE_DIR}/own-namespace-demo.yaml
25+
26+
# apply cluster extension
27+
kubectl apply -f ${DEMO_RESOURCE_DIR}/own-namespace-demo.yaml
28+
29+
# wait for cluster extension installation to succeed
30+
kubectl wait --for=condition=Installed clusterextension/argocd-operator --timeout="60s"
31+
32+
# check argocd-operator controller deployment pod template olm.targetNamespaces annotation
33+
kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.metadata.annotations.olm\.targetNamespaces}"
34+
35+
# check for argocd-operator rbac in watch namespace
36+
kubectl get roles,rolebindings -n argocd-system -o name
37+
38+
# get controller service-account name
39+
kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.spec.serviceAccount}"
40+
41+
# check service account for role binding is the same as controller service-account
42+
rolebinding=$(kubectl get rolebindings -n argocd-system -o name | grep 'argocd-operator' | head -n 1)
43+
kubectl get -n argocd-system $rolebinding -o jsonpath='{.subjects}' | jq .[0]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: olm.operatorframework.io/v1
2+
kind: ClusterExtension
3+
metadata:
4+
name: argocd-operator
5+
annotations:
6+
# watch namespace is the same as intall namespace
7+
olm.operatorframework.io/watch-namespace: argocd-system
8+
spec:
9+
namespace: argocd-system
10+
serviceAccount:
11+
name: argocd-installer
12+
source:
13+
sourceType: Catalog
14+
catalog:
15+
packageName: argocd-operator
16+
version: 0.6.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: olm.operatorframework.io/v1
2+
kind: ClusterExtension
3+
metadata:
4+
name: argocd-operator
5+
annotations:
6+
# watch-namespace is different from install namespace
7+
olm.operatorframework.io/watch-namespace: argocd
8+
spec:
9+
namespace: argocd-system
10+
serviceAccount:
11+
name: argocd-installer
12+
source:
13+
sourceType: Catalog
14+
catalog:
15+
packageName: argocd-operator
16+
version: 0.6.0

hack/demo/single-own-namespace.sh

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
#
4+
# Welcome to the SingleNamespace install mode demo
5+
#
6+
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
7+
8+
# enable 'SingleOwnNamespaceInstallSupport' feature gate
9+
kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=SingleOwnNamespaceInstallSupport=true"}]'
10+
11+
# wait for operator-controller to become available
12+
kubectl rollout status -n olmv1-system deployment/operator-controller-controller-manager
13+
14+
# create install namespace
15+
kubectl create ns argocd-system
16+
17+
# create installer service account
18+
kubectl create serviceaccount -n argocd-system argocd-installer
19+
20+
# give installer service account admin privileges (not for production environments)
21+
kubectl create clusterrolebinding argocd-installer-crb --clusterrole=cluster-admin --serviceaccount=argocd-system:argocd-installer
22+
23+
# create watch namespace
24+
kubectl create namespace argocd
25+
26+
# install cluster extension in single namespace install mode (watch namespace != install namespace)
27+
cat ${DEMO_RESOURCE_DIR}/single-namespace-demo.yaml
28+
29+
# apply cluster extension
30+
kubectl apply -f ${DEMO_RESOURCE_DIR}/single-namespace-demo.yaml
31+
32+
# wait for cluster extension installation to succeed
33+
kubectl wait --for=condition=Installed clusterextension/argocd-operator --timeout="60s"
34+
35+
# check argocd-operator controller deployment pod template olm.targetNamespaces annotation
36+
kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.metadata.annotations.olm\.targetNamespaces}"
37+
38+
# check for argocd-operator rbac in watch namespace
39+
kubectl get roles,rolebindings -n argocd -o name
40+
41+
# get controller service-account name
42+
kubectl get deployments -n argocd-system argocd-operator-controller-manager -o jsonpath="{.spec.template.spec.serviceAccount}"
43+
44+
# check service account for role binding is the controller deployment service account
45+
rolebinding=$(kubectl get rolebindings -n argocd -o name | grep 'argocd-operator' | head -n 1)
46+
kubectl get -n argocd $rolebinding -o jsonpath='{.subjects}' | jq .[0]

internal/operator-controller/applier/helm.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"helm.sh/helm/v3/pkg/postrender"
1616
"helm.sh/helm/v3/pkg/release"
1717
"helm.sh/helm/v3/pkg/storage/driver"
18-
corev1 "k8s.io/api/core/v1"
1918
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
2120
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -25,7 +24,6 @@ import (
2524

2625
ocv1 "github.com/operator-framework/operator-controller/api/v1"
2726
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
28-
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/convert"
2927
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety"
3028
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
3129
)
@@ -53,9 +51,12 @@ type Preflight interface {
5351
Upgrade(context.Context, *release.Release) error
5452
}
5553

54+
type BundleToHelmChartFn func(rv1 fs.FS, installNamespace string, watchNamespace string) (*chart.Chart, error)
55+
5656
type Helm struct {
57-
ActionClientGetter helmclient.ActionClientGetter
58-
Preflights []Preflight
57+
ActionClientGetter helmclient.ActionClientGetter
58+
Preflights []Preflight
59+
BundleToHelmChartFn BundleToHelmChartFn
5960
}
6061

6162
// shouldSkipPreflight is a helper to determine if the preflight check is CRDUpgradeSafety AND
@@ -79,7 +80,7 @@ func shouldSkipPreflight(ctx context.Context, preflight Preflight, ext *ocv1.Clu
7980
}
8081

8182
func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels map[string]string, storageLabels map[string]string) ([]client.Object, string, error) {
82-
chrt, err := convert.RegistryV1ToHelmChart(ctx, contentFS, ext.Spec.Namespace, []string{corev1.NamespaceAll})
83+
chrt, err := h.buildHelmChart(contentFS, ext)
8384
if err != nil {
8485
return nil, "", err
8586
}
@@ -152,6 +153,17 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
152153
return relObjects, state, nil
153154
}
154155

156+
func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*chart.Chart, error) {
157+
if h.BundleToHelmChartFn == nil {
158+
return nil, errors.New("BundleToHelmChartFn is nil")
159+
}
160+
watchNamespace, err := GetWatchNamespace(ext)
161+
if err != nil {
162+
return nil, err
163+
}
164+
return h.BundleToHelmChartFn(bundleFS, ext.Spec.Namespace, watchNamespace)
165+
}
166+
155167
func (h *Helm) getReleaseState(cl helmclient.ActionInterface, ext *ocv1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post postrender.PostRenderer) (*release.Release, *release.Release, string, error) {
156168
currentRelease, err := cl.Get(ext.GetName())
157169
if errors.Is(err, driver.ErrReleaseNotFound) {

0 commit comments

Comments
 (0)