Skip to content

Commit 7df3df3

Browse files
committed
Make the order of the RBAC Rules stable
Currently, the `role.yaml` generated by multiple runs of controller-gen may have different Rule orders. This commit makes the Rule order in `role.yaml` stable. Signed-off-by: Haiyan Meng <[email protected]>
1 parent c4d99a1 commit 7df3df3

File tree

6 files changed

+200
-9
lines changed

6 files changed

+200
-9
lines changed

pkg/rbac/parser.go

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ limitations under the License.
2323
package rbac
2424

2525
import (
26+
"fmt"
2627
"sort"
2728
"strings"
2829

@@ -54,6 +55,17 @@ type ruleKey struct {
5455
URLs string
5556
}
5657

58+
func (key ruleKey) String() string {
59+
return fmt.Sprintf("%s + %s + %s", key.Groups, key.Resources, key.URLs)
60+
}
61+
62+
// ruleKeys implements sort.Interface
63+
type ruleKeys []ruleKey
64+
65+
func (keys ruleKeys) Len() int { return len(keys) }
66+
func (keys ruleKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] }
67+
func (keys ruleKeys) Less(i, j int) bool { return keys[i].String() < keys[j].String() }
68+
5769
// key normalizes the Rule and returns a ruleKey object.
5870
func (r *Rule) key() ruleKey {
5971
r.normalize()
@@ -121,14 +133,16 @@ func (Generator) RegisterMarkers(into *markers.Registry) error {
121133
return into.Register(RuleDefinition)
122134
}
123135

124-
func (g Generator) Generate(ctx *genall.GenerationContext) error {
136+
// GenerateClusterRole generates a rbacv1.ClusterRole object
137+
func GenerateClusterRole(ctx *genall.GenerationContext, roleName string) (*rbacv1.ClusterRole, error) {
125138
rules := make(map[ruleKey]*Rule)
126139
for _, root := range ctx.Roots {
127140
markerSet, err := markers.PackageMarkers(ctx.Collector, root)
128141
if err != nil {
129142
root.AddError(err)
130143
}
131144

145+
// all the Rules having the same ruleKey will be merged into the first Rule
132146
for _, markerValue := range markerSet[RuleDefinition.Name] {
133147
rule := markerValue.(Rule)
134148
key := rule.key()
@@ -140,26 +154,46 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
140154
}
141155
}
142156

143-
var policyRules []rbacv1.PolicyRule
144-
for _, rule := range rules {
145-
policyRules = append(policyRules, rule.ToRule())
157+
if len(rules) == 0 {
158+
return nil, nil
159+
}
146160

161+
// sort the Rules in rules according to their ruleKeys
162+
keys := make([]ruleKey, 0)
163+
for key, _ := range rules {
164+
keys = append(keys, key)
147165
}
166+
sort.Sort(ruleKeys(keys))
167+
168+
var policyRules []rbacv1.PolicyRule
169+
for _, key := range keys {
170+
policyRules = append(policyRules, rules[key].ToRule())
148171

149-
if len(policyRules) == 0 {
150-
return nil
151172
}
152173

153-
if err := ctx.WriteYAML("role.yaml", rbacv1.ClusterRole{
174+
return &rbacv1.ClusterRole{
154175
TypeMeta: metav1.TypeMeta{
155176
Kind: "ClusterRole",
156177
APIVersion: rbacv1.SchemeGroupVersion.String(),
157178
},
158179
ObjectMeta: metav1.ObjectMeta{
159-
Name: g.RoleName,
180+
Name: roleName,
160181
},
161182
Rules: policyRules,
162-
}); err != nil {
183+
}, nil
184+
}
185+
186+
func (g Generator) Generate(ctx *genall.GenerationContext) error {
187+
clusterRole, err := GenerateClusterRole(ctx, g.RoleName)
188+
if err != nil {
189+
return err
190+
}
191+
192+
if clusterRole == nil {
193+
return nil
194+
}
195+
196+
if err := ctx.WriteYAML("role.yaml", *clusterRole); err != nil {
163197
return err
164198
}
165199

pkg/rbac/parser_integration_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package rbac_test
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
7+
"github.com/google/go-cmp/cmp"
8+
. "github.com/onsi/ginkgo"
9+
. "github.com/onsi/gomega"
10+
"sigs.k8s.io/yaml"
11+
12+
rbacv1 "k8s.io/api/rbac/v1"
13+
14+
"sigs.k8s.io/controller-tools/pkg/genall"
15+
"sigs.k8s.io/controller-tools/pkg/loader"
16+
"sigs.k8s.io/controller-tools/pkg/markers"
17+
"sigs.k8s.io/controller-tools/pkg/rbac"
18+
)
19+
20+
var _ = Describe("ClusterRole generated by the RBAC Generator", func() {
21+
It("should match the expected result", func() {
22+
By("switching into testdata to appease go modules")
23+
cwd, err := os.Getwd()
24+
Expect(err).NotTo(HaveOccurred())
25+
Expect(os.Chdir("./testdata")).To(Succeed()) // go modules are directory-sensitive
26+
defer func() { Expect(os.Chdir(cwd)).To(Succeed()) }()
27+
28+
By("loading the roots")
29+
pkgs, err := loader.LoadRoots(".")
30+
Expect(err).NotTo(HaveOccurred())
31+
32+
By("registering RBAC rule marker")
33+
reg := &markers.Registry{}
34+
Expect(reg.Register(rbac.RuleDefinition)).To(Succeed())
35+
36+
By("creating GenerationContext")
37+
ctx := &genall.GenerationContext{
38+
Collector: &markers.Collector{Registry: reg},
39+
Roots: pkgs,
40+
}
41+
42+
By("generating a ClusterRole")
43+
clusterRole, err := rbac.GenerateClusterRole(ctx, "manager-role")
44+
Expect(err).NotTo(HaveOccurred())
45+
46+
By("loading the desired YAML")
47+
expectedFile, err := ioutil.ReadFile("role.yaml")
48+
Expect(err).NotTo(HaveOccurred())
49+
50+
By("parsing the desired YAML")
51+
var role rbacv1.ClusterRole
52+
Expect(yaml.Unmarshal(expectedFile, &role)).To(Succeed())
53+
54+
By("comparing the two")
55+
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))
56+
})
57+
})

pkg/rbac/rbac_suite_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2019 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+
17+
package rbac_test
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestCRDGeneration(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "RBAC Generation Suite")
29+
}

pkg/rbac/testdata/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# RBAC Integration Test testdata
2+
3+
This contains a tiny module used for testdata for the RBAC integration
4+
test. The directory should always be called testdata, so Go treats it
5+
specially.
6+
7+
If you add a new marker, re-generate the golden output file,
8+
`role.yaml`, with:
9+
10+
```bash
11+
$ /path/to/current/build/of/controller-gen rbac:roleName=manager-role paths=. output:dir=.
12+
```
13+
14+
Make sure you review the diff to ensure that it only contains the desired
15+
changes!
16+
17+
If you didn't add a new marker and this output changes, make sure you have
18+
a good explanation for why generated output needs to change!

pkg/rbac/testdata/controller.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package controller
2+
3+
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs,verbs=get;watch;create
4+
// +kubebuilder:rbac:groups=batch.io,resources=cronjobs/status,verbs=get;update;patch
5+
// +kubebuilder:rbac:groups=art,resources=jobs,verbs=get
6+
// +kubebuilder:rbac:groups=batch;batch;batch,resources=jobs/status,verbs=watch
7+
// +kubebuilder:rbac:groups=batch;cron,resources=jobs/status,verbs=create;get
8+
// +kubebuilder:rbac:groups=cron;batch,resources=jobs/status,verbs=get;create
9+
// +kubebuilder:rbac:groups=batch,resources=jobs/status,verbs=watch;watch

pkg/rbac/testdata/role.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
---
3+
apiVersion: rbac.authorization.k8s.io/v1
4+
kind: ClusterRole
5+
metadata:
6+
creationTimestamp: null
7+
name: manager-role
8+
rules:
9+
- apiGroups:
10+
- art
11+
resources:
12+
- jobs
13+
verbs:
14+
- get
15+
- apiGroups:
16+
- batch
17+
resources:
18+
- jobs/status
19+
verbs:
20+
- watch
21+
- apiGroups:
22+
- batch
23+
- cron
24+
resources:
25+
- jobs/status
26+
verbs:
27+
- create
28+
- get
29+
- apiGroups:
30+
- batch.io
31+
resources:
32+
- cronjobs
33+
verbs:
34+
- create
35+
- get
36+
- watch
37+
- apiGroups:
38+
- batch.io
39+
resources:
40+
- cronjobs/status
41+
verbs:
42+
- get
43+
- patch
44+
- update

0 commit comments

Comments
 (0)