Skip to content

Commit a0b2e93

Browse files
authored
Merge pull request #260 from haiyanmeng/role
Add a new `namespace` field into the RBAC marker
2 parents 46695ab + b459de6 commit a0b2e93

File tree

6 files changed

+142
-42
lines changed

6 files changed

+142
-42
lines changed

pkg/rbac/parser.go

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type Rule struct {
5252
Verbs []string
5353
// URL specifies the non-resource URLs that this rule encompasses.
5454
URLs []string `marker:"urls,optional"`
55+
// Namespace specifies the scope of the Rule.
56+
// If not set, the Rule belongs to the generated ClusterRole.
57+
// If set, the Rule belongs to a Role, whose namespace is specified by this field.
58+
Namespace string `marker:",optional"`
5559
}
5660

5761
// ruleKey represents the resources and non-resources a Rule applies.
@@ -146,69 +150,109 @@ func (Generator) RegisterMarkers(into *markers.Registry) error {
146150
return nil
147151
}
148152

149-
// GenerateClusterRole generates a rbacv1.ClusterRole object
150-
func GenerateClusterRole(ctx *genall.GenerationContext, roleName string) (*rbacv1.ClusterRole, error) {
151-
rules := make(map[ruleKey]*Rule)
153+
// GenerateRoles generate a slice of objs representing either a ClusterRole or a Role object
154+
// The order of the objs in the returned slice is stable and determined by their namespaces.
155+
func GenerateRoles(ctx *genall.GenerationContext, roleName string) ([]interface{}, error) {
156+
rulesByNS := make(map[string][]*Rule)
152157
for _, root := range ctx.Roots {
153158
markerSet, err := markers.PackageMarkers(ctx.Collector, root)
154159
if err != nil {
155160
root.AddError(err)
156161
}
157162

158-
// all the Rules having the same ruleKey will be merged into the first Rule
163+
// group RBAC markers by namespace
159164
for _, markerValue := range markerSet[RuleDefinition.Name] {
160165
rule := markerValue.(Rule)
166+
namespace := rule.Namespace
167+
if _, ok := rulesByNS[namespace]; !ok {
168+
rules := make([]*Rule, 0)
169+
rulesByNS[namespace] = rules
170+
}
171+
rulesByNS[namespace] = append(rulesByNS[namespace], &rule)
172+
}
173+
}
174+
175+
// NormalizeRules merge Rule with the same ruleKey and sort the Rules
176+
NormalizeRules := func(rules []*Rule) []rbacv1.PolicyRule {
177+
ruleMap := make(map[ruleKey]*Rule)
178+
// all the Rules having the same ruleKey will be merged into the first Rule
179+
for _, rule := range rules {
161180
key := rule.key()
162-
if _, ok := rules[key]; !ok {
163-
rules[key] = &rule
181+
if _, ok := ruleMap[key]; !ok {
182+
ruleMap[key] = rule
164183
continue
165184
}
166-
rules[key].addVerbs(rule.Verbs)
185+
ruleMap[key].addVerbs(rule.Verbs)
167186
}
168-
}
169187

170-
if len(rules) == 0 {
171-
return nil, nil
172-
}
188+
// sort the Rules in rules according to their ruleKeys
189+
keys := make([]ruleKey, 0, len(ruleMap))
190+
for key := range ruleMap {
191+
keys = append(keys, key)
192+
}
193+
sort.Sort(ruleKeys(keys))
173194

174-
// sort the Rules in rules according to their ruleKeys
175-
keys := make([]ruleKey, 0)
176-
for key := range rules {
177-
keys = append(keys, key)
178-
}
179-
sort.Sort(ruleKeys(keys))
195+
var policyRules []rbacv1.PolicyRule
196+
for _, key := range keys {
197+
policyRules = append(policyRules, ruleMap[key].ToRule())
180198

181-
var policyRules []rbacv1.PolicyRule
182-
for _, key := range keys {
183-
policyRules = append(policyRules, rules[key].ToRule())
199+
}
200+
return policyRules
201+
}
184202

203+
// collect all the namespaces and sort them
204+
var namespaces []string
205+
for ns := range rulesByNS {
206+
namespaces = append(namespaces, ns)
207+
}
208+
sort.Strings(namespaces)
209+
210+
// process the items in rulesByNS by the order specified in `namespaces` to make sure that the Role order is stable
211+
var objs []interface{}
212+
for _, ns := range namespaces {
213+
rules := rulesByNS[ns]
214+
policyRules := NormalizeRules(rules)
215+
if len(policyRules) == 0 {
216+
continue
217+
}
218+
if ns == "" {
219+
objs = append(objs, rbacv1.ClusterRole{
220+
TypeMeta: metav1.TypeMeta{
221+
Kind: "ClusterRole",
222+
APIVersion: rbacv1.SchemeGroupVersion.String(),
223+
},
224+
ObjectMeta: metav1.ObjectMeta{
225+
Name: roleName,
226+
},
227+
Rules: policyRules,
228+
})
229+
} else {
230+
objs = append(objs, rbacv1.Role{
231+
TypeMeta: metav1.TypeMeta{
232+
Kind: "Role",
233+
APIVersion: rbacv1.SchemeGroupVersion.String(),
234+
},
235+
ObjectMeta: metav1.ObjectMeta{
236+
Name: roleName,
237+
Namespace: ns,
238+
},
239+
Rules: policyRules,
240+
})
241+
}
185242
}
186243

187-
return &rbacv1.ClusterRole{
188-
TypeMeta: metav1.TypeMeta{
189-
Kind: "ClusterRole",
190-
APIVersion: rbacv1.SchemeGroupVersion.String(),
191-
},
192-
ObjectMeta: metav1.ObjectMeta{
193-
Name: roleName,
194-
},
195-
Rules: policyRules,
196-
}, nil
244+
return objs, nil
197245
}
198246

199247
func (g Generator) Generate(ctx *genall.GenerationContext) error {
200-
clusterRole, err := GenerateClusterRole(ctx, g.RoleName)
248+
objs, err := GenerateRoles(ctx, g.RoleName)
201249
if err != nil {
202250
return err
203251
}
204252

205-
if clusterRole == nil {
253+
if len(objs) == 0 {
206254
return nil
207255
}
208256

209-
if err := ctx.WriteYAML("role.yaml", *clusterRole); err != nil {
210-
return err
211-
}
212-
213-
return nil
257+
return ctx.WriteYAML("role.yaml", objs...)
214258
}

pkg/rbac/parser_integration_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package rbac_test
22

33
import (
4+
"bytes"
5+
"fmt"
46
"io/ioutil"
57
"os"
68

@@ -43,19 +45,29 @@ var _ = Describe("ClusterRole generated by the RBAC Generator", func() {
4345
}
4446

4547
By("generating a ClusterRole")
46-
clusterRole, err := rbac.GenerateClusterRole(ctx, "manager-role")
48+
objs, err := rbac.GenerateRoles(ctx, "manager-role")
4749
Expect(err).NotTo(HaveOccurred())
4850

4951
By("loading the desired YAML")
5052
expectedFile, err := ioutil.ReadFile("role.yaml")
5153
Expect(err).NotTo(HaveOccurred())
5254

5355
By("parsing the desired YAML")
54-
var role rbacv1.ClusterRole
55-
Expect(yaml.Unmarshal(expectedFile, &role)).To(Succeed())
56+
for i, expectedRoleBytes := range bytes.Split(expectedFile, []byte("\n---\n"))[1:] {
57+
By(fmt.Sprintf("comparing the generated Role and expected Role (Pair %d)", i))
58+
obj := objs[i]
59+
switch obj := obj.(type) {
60+
case rbacv1.ClusterRole:
61+
var expectedClusterRole rbacv1.ClusterRole
62+
Expect(yaml.Unmarshal(expectedRoleBytes, &expectedClusterRole)).To(Succeed())
63+
Expect(obj).To(Equal(expectedClusterRole), "type not as expected, check pkg/rbac/testdata/README.md for more details.\n\nDiff:\n\n%s", cmp.Diff(obj, expectedClusterRole))
64+
default:
65+
var expectedRole rbacv1.Role
66+
Expect(yaml.Unmarshal(expectedRoleBytes, &expectedRole)).To(Succeed())
67+
Expect(obj).To(Equal(expectedRole), "type not as expected, check pkg/rbac/testdata/README.md for more details.\n\nDiff:\n\n%s", cmp.Diff(obj, expectedRole))
68+
}
69+
}
5670

57-
By("comparing the two")
58-
Expect(*clusterRole).To(Equal(role), "type not as expected, check pkg/rbac/testdata/README.md for more details.\n\nDiff:\n\n%s", cmp.Diff(*clusterRole, role))
5971
})
6072
}
6173
})

pkg/rbac/testdata/controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package controller
33
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs,verbs=get;watch;create
44
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs/status,verbs=get;update;patch
55
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get
6+
// +kubebuilder:rbac:groups=wave,resources=jobs,verbs=get,namespace=zoo
67
// +kubebuilder:rbac:groups=batch;batch;batch,resources=jobs/status,verbs=watch
78
// +kubebuilder:rbac:groups=batch;cron,resources=jobs/status,verbs=create;get
9+
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get,namespace=zoo
810
// +kubebuilder:rbac:groups=cron;batch,resources=jobs/status,verbs=get;create
911
// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=watch;watch
12+
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get,namespace=park

pkg/rbac/testdata/role.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,39 @@ rules:
4242
- get
4343
- patch
4444
- update
45+
46+
---
47+
apiVersion: rbac.authorization.k8s.io/v1
48+
kind: Role
49+
metadata:
50+
creationTimestamp: null
51+
name: manager-role
52+
namespace: park
53+
rules:
54+
- apiGroups:
55+
- art
56+
resources:
57+
- jobs
58+
verbs:
59+
- get
60+
61+
---
62+
apiVersion: rbac.authorization.k8s.io/v1
63+
kind: Role
64+
metadata:
65+
creationTimestamp: null
66+
name: manager-role
67+
namespace: zoo
68+
rules:
69+
- apiGroups:
70+
- art
71+
resources:
72+
- jobs
73+
verbs:
74+
- get
75+
- apiGroups:
76+
- wave
77+
resources:
78+
- jobs
79+
verbs:
80+
- get

pkg/rbac/zz_generated.markerhelp.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func (Rule) Help() *markers.DefinitionHelp {
6464
Summary: "URL specifies the non-resource URLs that this rule encompasses.",
6565
Details: "",
6666
},
67+
"Namespace": markers.DetailedHelp{
68+
Summary: "specifies the scope of the Rule. If not set, the Rule belongs to the generated ClusterRole. If set, the Rule belongs to a Role, whose namespace is specified by this field.",
69+
Details: "",
70+
},
6771
},
6872
}
6973
}

test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ golangci-lint run --disable-all \
123123
--enable=misspell \
124124
--enable=gocyclo \
125125
--enable=gosec \
126+
--deadline=5m \
126127
./pkg/... ./cmd/...
127128

128129
# --enable=structcheck \ # doesn't understand embedded structs

0 commit comments

Comments
 (0)