Skip to content

Commit 9025ebf

Browse files
committed
added unit test for RBAC annotation validation
1 parent 6ce0980 commit 9025ebf

File tree

4 files changed

+119
-8
lines changed

4 files changed

+119
-8
lines changed

cmd/internal/codegen/parse/parser.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package parse
1818

1919
import (
2020
"bufio"
21+
"fmt"
2122
"go/build"
2223
"log"
2324
"os"
@@ -80,14 +81,21 @@ func NewAPIs(context *generator.Context, arguments *args.GeneratorArgs) *APIs {
8081
// e.g. if there is an // +kubebuilder:informer annotation for Pods, then there
8182
// should also be a // +kubebuilder:rbac annotation for Pods
8283
func (b *APIs) verifyRBACAnnotations() {
84+
err := rbacMatchesInformers(b.Informers, b.Rules)
85+
if err != nil {
86+
log.Fatal(err)
87+
}
88+
}
89+
90+
func rbacMatchesInformers(informers map[v1.GroupVersionKind]bool, rbacRules []rbacv1.PolicyRule) error {
8391
rs := inflect.NewDefaultRuleset()
8492

8593
// For each informer, look for the RBAC annotation
86-
for gvk := range b.Informers {
94+
for gvk := range informers {
8795
found := false
8896

8997
// Search all RBAC rules for one that matches the informer group and resource
90-
for _, rule := range b.Rules {
98+
for _, rule := range rbacRules {
9199

92100
// Check if the group matches the informer group
93101
groupFound := false
@@ -112,12 +120,11 @@ func (b *APIs) verifyRBACAnnotations() {
112120
continue
113121
}
114122

123+
// The resource name is the lower-plural of the Kind
124+
resource := rs.Pluralize(strings.ToLower(gvk.Kind))
115125
// Check if the resource matches the informer resource
116126
resourceFound := false
117127
for _, k := range rule.Resources {
118-
// The resource name is the lower-plural of the Kind
119-
resource := rs.Pluralize(strings.ToLower(gvk.Kind))
120-
121128
// If the RBAC resource is a wildcard or matches the informer resource, it is a match
122129
if k == "*" || k == resource {
123130
resourceFound = true
@@ -133,11 +140,12 @@ func (b *APIs) verifyRBACAnnotations() {
133140
break
134141
}
135142
if !found {
136-
log.Fatalf("Missing rbac rule for %s.%s. Add with // +kubebuilder:rbac:groups=%s,"+
143+
return fmt.Errorf("Missing rbac rule for %s.%s. Add with // +kubebuilder:rbac:groups=%s,"+
137144
"resources=%s,verbs=get;list;watch", gvk.Group, gvk.Kind, gvk.Group,
138145
inflect.NewDefaultRuleset().Pluralize(strings.ToLower(gvk.Kind)))
139146
}
140147
}
148+
return nil
141149
}
142150

143151
// parseGroupNames initializes b.GroupNames with the set of all groups
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package parse
17+
18+
import (
19+
"testing"
20+
21+
rbacv1 "k8s.io/api/rbac/v1"
22+
"k8s.io/apimachinery/pkg/apis/meta/v1"
23+
)
24+
25+
func TestrbacMatchesInformers(t *testing.T) {
26+
tests := []struct {
27+
informers map[v1.GroupVersionKind]bool
28+
rbacRules []rbacv1.PolicyRule
29+
expErr bool
30+
}{
31+
{
32+
// informer resource matches the RBAC rule
33+
informers: map[v1.GroupVersionKind]bool{
34+
v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true,
35+
},
36+
rbacRules: []rbacv1.PolicyRule{
37+
{APIGroups: []string{"apps"}, Resources: []string{"deployments"}},
38+
},
39+
expErr: false,
40+
},
41+
{
42+
// RBAC rule does not match the informer resource because of missing pluralization in RBAC rules
43+
informers: map[v1.GroupVersionKind]bool{
44+
v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true,
45+
},
46+
rbacRules: []rbacv1.PolicyRule{
47+
{APIGroups: []string{"apps"}, Resources: []string{"Deployment"}},
48+
},
49+
expErr: true,
50+
},
51+
{
52+
// wild-card RBAC rule should match any resource in the group
53+
informers: map[v1.GroupVersionKind]bool{
54+
v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}: true,
55+
v1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}: true,
56+
},
57+
rbacRules: []rbacv1.PolicyRule{
58+
{APIGroups: []string{"apps"}, Resources: []string{"*"}},
59+
},
60+
expErr: false,
61+
},
62+
{
63+
// empty group name is normalized to "core"
64+
informers: map[v1.GroupVersionKind]bool{
65+
v1.GroupVersionKind{Group: "core", Version: "v1", Kind: "Pod"}: true,
66+
},
67+
rbacRules: []rbacv1.PolicyRule{
68+
{APIGroups: []string{""}, Resources: []string{"pods"}},
69+
},
70+
expErr: false,
71+
},
72+
{
73+
// empty group name is normalized to "core"
74+
informers: map[v1.GroupVersionKind]bool{
75+
v1.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}: true,
76+
},
77+
rbacRules: []rbacv1.PolicyRule{
78+
{APIGroups: []string{"core"}, Resources: []string{"pods"}},
79+
},
80+
expErr: false,
81+
},
82+
}
83+
84+
for _, test := range tests {
85+
err := checkRBACMatchesInformers(test.informers, test.rbacRules)
86+
if test.expErr {
87+
if err == nil {
88+
t.Errorf("RBAC rules %+v shouldn't match with informers %+v", test.rbacRules, test.informers)
89+
}
90+
} else {
91+
if err != nil {
92+
t.Errorf("RBAC rules %+v should match informers %+v, but got a mismatch error: %v", test.rbacRules, test.informers, err)
93+
}
94+
}
95+
}
96+
}

cmd/kubebuilder/create/controller/controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func (bc *{{.Kind}}Controller) Reconcile(k types.ReconcileKey) error {
7979
return nil
8080
}
8181
{{if .CoreType}}// +kubebuilder:informers:group={{ .Group }},version={{ .Version }},kind={{ .Kind }}{{end}}
82+
{{if .CoreType}}// +kubebuilder:rbac:groups={{ .Group }},resources={{ .Resource }},verbs=get;watch;list{{end}}
8283
// +kubebuilder:controller:group={{ .Group }},version={{ .Version }},kind={{ .Kind}},resource={{ .Resource }}
8384
type {{.Kind}}Controller struct {
8485
// INSERT ADDITIONAL FIELDS HERE

test.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,18 @@ function prepare_testdir_under_gopath {
136136
header_text "running kubebuilder commands in test directory $kb_test_dir"
137137
}
138138

139-
function generate_crd_resources {
140-
header_text "generating CRD resources and code"
139+
function setup_envs {
140+
header_text "setting up env vars"
141141

142142
# Setup env vars
143143
export PATH=/tmp/kubebuilder/bin/:$PATH
144144
export TEST_ASSET_KUBECTL=/tmp/kubebuilder/bin/kubectl
145145
export TEST_ASSET_KUBE_APISERVER=/tmp/kubebuilder/bin/kube-apiserver
146146
export TEST_ASSET_ETCD=/tmp/kubebuilder/bin/etcd
147+
}
148+
149+
function generate_crd_resources {
150+
header_text "generating CRD resources and code"
147151

148152
# Run the commands
149153
kubebuilder init repo --domain sample.kubernetes.io
@@ -582,6 +586,8 @@ prepare_staging_dir
582586
fetch_tools
583587
build_kb
584588

589+
setup_envs
590+
585591
prepare_testdir_under_gopath
586592
generate_crd_resources
587593
generate_controller

0 commit comments

Comments
 (0)