Skip to content

Commit 8412ccb

Browse files
authored
commands/.../new.go;pkg/scaffold: adding support for ClusterRole and ClusterRoleBinding (#747)
* commands/.../new.go;pkg/scaffold: adding support for ClusterRole and ClusterRoleBinding * commands/.../new.go;pkg/scaffold: moving IsClusterScoped to Role and RoleBinding only * pkg/scaffold: set WATCH_NAMESPACE correctly and improve clarity of REPLACE_NAMESPACE * doc: improve docs on cluster scoped operators * commands/.../new,pkg/scaffold/ansible: pass isClusterScoped to operators * doc: fixing typos in operator scope section * doc/ansible/user-guide.md: updating to include --cluster-scoped docs
1 parent 5f4af2c commit 8412ccb

File tree

11 files changed

+203
-15
lines changed

11 files changed

+203
-15
lines changed

commands/operator-sdk/cmd/new.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ generates a skeletal app-operator application in $GOPATH/src/github.com/example.
5353
newCmd.Flags().StringVar(&operatorType, "type", "go", "Type of operator to initialize (e.g \"ansible\")")
5454
newCmd.Flags().BoolVar(&skipGit, "skip-git-init", false, "Do not init the directory as a git repository")
5555
newCmd.Flags().BoolVar(&generatePlaybook, "generate-playbook", false, "Generate a playbook skeleton. (Only used for --type ansible)")
56+
newCmd.Flags().BoolVar(&isClusterScoped, "cluster-scoped", false, "Generate cluster-scoped resources instead of namespace-scoped")
5657

5758
return newCmd
5859
}
@@ -64,6 +65,7 @@ var (
6465
projectName string
6566
skipGit bool
6667
generatePlaybook bool
68+
isClusterScoped bool
6769
)
6870

6971
const (
@@ -131,9 +133,15 @@ func doScaffold() {
131133
&scaffold.Cmd{},
132134
&scaffold.Dockerfile{},
133135
&scaffold.ServiceAccount{},
134-
&scaffold.Role{},
135-
&scaffold.RoleBinding{},
136-
&scaffold.Operator{},
136+
&scaffold.Role{
137+
IsClusterScoped: isClusterScoped,
138+
},
139+
&scaffold.RoleBinding{
140+
IsClusterScoped: isClusterScoped,
141+
},
142+
&scaffold.Operator{
143+
IsClusterScoped: isClusterScoped,
144+
},
137145
&scaffold.Apis{},
138146
&scaffold.Controller{},
139147
&scaffold.Version{},
@@ -177,9 +185,15 @@ func doAnsibleScaffold() {
177185
},
178186
galaxyInit,
179187
&scaffold.ServiceAccount{},
180-
&scaffold.Role{},
181-
&scaffold.RoleBinding{},
182-
&ansible.Operator{},
188+
&scaffold.Role{
189+
IsClusterScoped: isClusterScoped,
190+
},
191+
&scaffold.RoleBinding{
192+
IsClusterScoped: isClusterScoped,
193+
},
194+
&ansible.Operator{
195+
IsClusterScoped: isClusterScoped,
196+
},
183197
&scaffold.Crd{
184198
Resource: resource,
185199
},

doc/ansible/user-guide.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ Memcached resource with APIVersion `cache.example.com/v1apha1` and Kind
5353
To learn more about the project directory structure, see [project
5454
layout][layout_doc] doc.
5555

56+
#### Operator scope
57+
58+
A namespace-scoped operator (the default) watches and manages resources in a single namespace, whereas a cluster-scoped operator watches and manages resources cluster-wide. Namespace-scoped operators are preferred because of their flexibility. They enable decoupled upgrades, namespace isolation for failures and monitoring, and differing API definitions. However, there are use cases where a cluster-scoped operator may make sense. For example, the [cert-manager](https://github.com/jetstack/cert-manager) operator is often deployed with cluster-scoped permissions and watches so that it can manage issuing certificates for an entire cluster.
59+
60+
If you'd like to create your memcached-operator project to be cluster-scoped use the following `operator-sdk new` command instead:
61+
```
62+
$ operator-sdk new memcached-operator --cluster-scoped --api-version=cache.example.com/v1alpha1 --kind=Memcached --type=ansible
63+
```
64+
5665
## Customize the operator logic
5766

5867
For this example the memcached-operator will execute the following
@@ -205,10 +214,17 @@ deployment image in this file needs to be modified from the placeholder
205214
$ sed -i 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml
206215
```
207216

217+
If you created your operator using `--cluster-scoped=true`, update the service account namespace in the generated `ClusterRoleBinding` to match where you are deploying your operator.
218+
```
219+
$ export OPERATOR_NAMESPACE=$(kubectl config view --minify -o jsonpath='{.contexts[0].context.namespace}')
220+
$ sed -i "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml
221+
```
222+
208223
**Note**
209-
If you are performing these steps on OSX, use the following command:
224+
If you are performing these steps on OSX, use the following commands instead:
210225
```
211226
$ sed -i "" 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml
227+
$ sed -i "" "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml
212228
```
213229
214230
Deploy the memcached-operator:
@@ -220,10 +236,6 @@ $ kubectl create -f deploy/role_binding.yaml
220236
$ kubectl create -f deploy/operator.yaml
221237
```
222238

223-
**NOTE**: `deploy/rbac.yaml` creates a `ClusterRoleBinding` and assumes we are
224-
working in namespace `default`. If you are working in a different namespace you
225-
must modify this file before creating it.
226-
227239
Verify that the memcached-operator is up and running:
228240

229241
```sh

doc/sdk-cli-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Scaffolds a new operator project.
133133
* `--type` Type of operator to initialize: "ansible" or "go" (default "go"). Also requires the following flags if `--type=ansible`
134134
* `--api-version` CRD APIVersion in the format `$GROUP_NAME/$VERSION` (e.g app.example.com/v1alpha1)
135135
* `--kind` CRD Kind. (e.g AppService)
136+
* `--cluster-scoped` Initialize the operator to be cluster-scoped instead of namespace-scoped
136137
* `-h, --help` - help for new
137138

138139
### Example

doc/user-guide.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ $ cd memcached-operator
4747

4848
To learn about the project directory structure, see [project layout][layout_doc] doc.
4949

50+
#### Operator scope
51+
52+
A namespace-scoped operator (the default) watches and manages resources in a single namespace, whereas a cluster-scoped operator watches and manages resources cluster-wide. Namespace-scoped operators are preferred because of their flexibility. They enable decoupled upgrades, namespace isolation for failures and monitoring, and differing API definitions. However, there are use cases where a cluster-scoped operator may make sense. For example, the [cert-manager](https://github.com/jetstack/cert-manager) operator is often deployed with cluster-scoped permissions and watches so that it can manage issuing certificates for an entire cluster.
53+
54+
If you'd like to create your memcached-operator project to be cluster-scoped use the following `operator-sdk new` command instead:
55+
```
56+
$ operator-sdk new memcached-operator --cluster-scoped
57+
```
58+
5059
### Manager
5160
The main program for the operator `cmd/manager/main.go` initializes and runs the [Manager][manager_go_doc].
5261

@@ -193,10 +202,17 @@ $ sed -i 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/op
193202
$ docker push quay.io/example/memcached-operator:v0.0.1
194203
```
195204

205+
If you created your operator using `--cluster-scoped=true`, update the service account namespace in the generated `ClusterRoleBinding` to match where you are deploying your operator.
206+
```
207+
$ export OPERATOR_NAMESPACE=$(kubectl config view --minify -o jsonpath='{.contexts[0].context.namespace}')
208+
$ sed -i "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml
209+
```
210+
196211
**Note**
197-
If you are performing these steps on OSX, use the following command:
212+
If you are performing these steps on OSX, use the following commands instead:
198213
```
199214
$ sed -i "" 's|REPLACE_IMAGE|quay.io/example/memcached-operator:v0.0.1|g' deploy/operator.yaml
215+
$ sed -i "" "s|REPLACE_NAMESPACE|$OPERATOR_NAMESPACE|g" deploy/role_binding.yaml
200216
```
201217

202218
The Deployment manifest is generated at `deploy/operator.yaml`. Be sure to update the deployment image as shown above since the default is just a placeholder.

pkg/scaffold/ansible/operator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323

2424
type Operator struct {
2525
input.Input
26+
27+
IsClusterScoped bool
2628
}
2729

2830
func (s *Operator) GetInput() (input.Input, error) {
@@ -58,9 +60,13 @@ spec:
5860
imagePullPolicy: Always
5961
env:
6062
- name: WATCH_NAMESPACE
63+
{{- if .IsClusterScoped }}
64+
value: ""
65+
{{- else }}
6166
valueFrom:
6267
fieldRef:
6368
fieldPath: metadata.namespace
69+
{{- end}}
6470
- name: OPERATOR_NAME
6571
value: "{{.ProjectName}}"
6672
`

pkg/scaffold/operator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const OperatorYamlFile = "operator.yaml"
2424

2525
type Operator struct {
2626
input.Input
27+
28+
IsClusterScoped bool
2729
}
2830

2931
func (s *Operator) GetInput() (input.Input, error) {
@@ -61,9 +63,13 @@ spec:
6163
imagePullPolicy: Always
6264
env:
6365
- name: WATCH_NAMESPACE
66+
{{- if .IsClusterScoped }}
67+
value: ""
68+
{{- else }}
6469
valueFrom:
6570
fieldRef:
6671
fieldPath: metadata.namespace
72+
{{- end}}
6773
- name: POD_NAME
6874
valueFrom:
6975
fieldRef:

pkg/scaffold/operator_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ func TestOperator(t *testing.T) {
3333
}
3434
}
3535

36+
func TestOperatorClusterScoped(t *testing.T) {
37+
s, buf := setupScaffoldAndWriter()
38+
err := s.Execute(appConfig, &Operator{IsClusterScoped: true})
39+
if err != nil {
40+
t.Fatalf("failed to execute the scaffold: (%v)", err)
41+
}
42+
43+
if operatorClusterScopedExp != buf.String() {
44+
diffs := testutil.Diff(operatorClusterScopedExp, buf.String())
45+
t.Fatalf("expected vs actual differs.\n%v", diffs)
46+
}
47+
}
48+
3649
const operatorExp = `apiVersion: apps/v1
3750
kind: Deployment
3851
metadata:
@@ -70,3 +83,39 @@ spec:
7083
- name: OPERATOR_NAME
7184
value: "app-operator"
7285
`
86+
87+
const operatorClusterScopedExp = `apiVersion: apps/v1
88+
kind: Deployment
89+
metadata:
90+
name: app-operator
91+
spec:
92+
replicas: 1
93+
selector:
94+
matchLabels:
95+
name: app-operator
96+
template:
97+
metadata:
98+
labels:
99+
name: app-operator
100+
spec:
101+
serviceAccountName: app-operator
102+
containers:
103+
- name: app-operator
104+
# Replace this with the built image name
105+
image: REPLACE_IMAGE
106+
ports:
107+
- containerPort: 60000
108+
name: metrics
109+
command:
110+
- app-operator
111+
imagePullPolicy: Always
112+
env:
113+
- name: WATCH_NAMESPACE
114+
value: ""
115+
- name: POD_NAME
116+
valueFrom:
117+
fieldRef:
118+
fieldPath: metadata.name
119+
- name: OPERATOR_NAME
120+
value: "app-operator"
121+
`

pkg/scaffold/role.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const RoleYamlFile = "role.yaml"
3434

3535
type Role struct {
3636
input.Input
37+
38+
IsClusterScoped bool
3739
}
3840

3941
func (s *Role) GetInput() (input.Input, error) {
@@ -148,7 +150,7 @@ func UpdateRoleForResource(r *Resource, absProjectPath string) error {
148150
return nil
149151
}
150152

151-
const roleTemplate = `kind: Role
153+
const roleTemplate = `kind: {{if .IsClusterScoped}}Cluster{{end}}Role
152154
apiVersion: rbac.authorization.k8s.io/v1
153155
metadata:
154156
name: {{.ProjectName}}

pkg/scaffold/role_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ func TestRole(t *testing.T) {
3333
}
3434
}
3535

36+
func TestRoleClusterScoped(t *testing.T) {
37+
s, buf := setupScaffoldAndWriter()
38+
err := s.Execute(appConfig, &Role{IsClusterScoped: true})
39+
if err != nil {
40+
t.Fatalf("failed to execute the scaffold: (%v)", err)
41+
}
42+
43+
if clusterroleExp != buf.String() {
44+
diffs := testutil.Diff(clusterroleExp, buf.String())
45+
t.Fatalf("expected vs actual differs.\n%v", diffs)
46+
}
47+
}
48+
3649
const roleExp = `kind: Role
3750
apiVersion: rbac.authorization.k8s.io/v1
3851
metadata:
@@ -67,3 +80,38 @@ rules:
6780
- "get"
6881
- "create"
6982
`
83+
84+
const clusterroleExp = `kind: ClusterRole
85+
apiVersion: rbac.authorization.k8s.io/v1
86+
metadata:
87+
name: app-operator
88+
rules:
89+
- apiGroups:
90+
- ""
91+
resources:
92+
- pods
93+
- services
94+
- endpoints
95+
- persistentvolumeclaims
96+
- events
97+
- configmaps
98+
- secrets
99+
verbs:
100+
- "*"
101+
- apiGroups:
102+
- apps
103+
resources:
104+
- deployments
105+
- daemonsets
106+
- replicasets
107+
- statefulsets
108+
verbs:
109+
- "*"
110+
- apiGroups:
111+
- monitoring.coreos.com
112+
resources:
113+
- servicemonitors
114+
verbs:
115+
- "get"
116+
- "create"
117+
`

pkg/scaffold/rolebinding.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const RoleBindingYamlFile = "role_binding.yaml"
2424

2525
type RoleBinding struct {
2626
input.Input
27+
28+
IsClusterScoped bool
2729
}
2830

2931
func (s *RoleBinding) GetInput() (input.Input, error) {
@@ -34,15 +36,19 @@ func (s *RoleBinding) GetInput() (input.Input, error) {
3436
return s.Input, nil
3537
}
3638

37-
const roleBindingTemplate = `kind: RoleBinding
39+
const roleBindingTemplate = `kind: {{if .IsClusterScoped}}Cluster{{end}}RoleBinding
3840
apiVersion: rbac.authorization.k8s.io/v1
3941
metadata:
4042
name: {{.ProjectName}}
4143
subjects:
4244
- kind: ServiceAccount
4345
name: {{.ProjectName}}
46+
{{- if .IsClusterScoped }}
47+
# Replace this with the namespace the operator is deployed in.
48+
namespace: REPLACE_NAMESPACE
49+
{{- end }}
4450
roleRef:
45-
kind: Role
51+
kind: {{if .IsClusterScoped}}Cluster{{end}}Role
4652
name: {{.ProjectName}}
4753
apiGroup: rbac.authorization.k8s.io
4854
`

pkg/scaffold/rolebinding_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ func TestRoleBinding(t *testing.T) {
3333
}
3434
}
3535

36+
func TestRoleBindingClusterScoped(t *testing.T) {
37+
s, buf := setupScaffoldAndWriter()
38+
err := s.Execute(appConfig, &RoleBinding{IsClusterScoped: true})
39+
if err != nil {
40+
t.Fatalf("failed to execute the scaffold: (%v)", err)
41+
}
42+
43+
if clusterrolebindingExp != buf.String() {
44+
diffs := testutil.Diff(clusterrolebindingExp, buf.String())
45+
t.Fatalf("expected vs actual differs.\n%v", diffs)
46+
}
47+
}
48+
3649
const rolebindingExp = `kind: RoleBinding
3750
apiVersion: rbac.authorization.k8s.io/v1
3851
metadata:
@@ -45,3 +58,18 @@ roleRef:
4558
name: app-operator
4659
apiGroup: rbac.authorization.k8s.io
4760
`
61+
62+
const clusterrolebindingExp = `kind: ClusterRoleBinding
63+
apiVersion: rbac.authorization.k8s.io/v1
64+
metadata:
65+
name: app-operator
66+
subjects:
67+
- kind: ServiceAccount
68+
name: app-operator
69+
# Replace this with the namespace the operator is deployed in.
70+
namespace: REPLACE_NAMESPACE
71+
roleRef:
72+
kind: ClusterRole
73+
name: app-operator
74+
apiGroup: rbac.authorization.k8s.io
75+
`

0 commit comments

Comments
 (0)