Repository: kubernetes-sigs/controller-tools
Pull request #234 (2019-06-27 17:27:39)

Add support for arrays as map values into pkg/crd
<!-- please add a icon to the title of this PR (see VERSIONING.md), and delete this line and similar ones -->
<!-- the icon will be either ⚠ (:warning:, major), ✨ (:sparkles, minor), 🐛 (:bug:, patch), 📖 (:book:, docs), or 🏃 (:running:, other) -->

<!-- What does this do, and why do we need it? -->
Fix: https://github.com/kubernetes-sigs/controller-tools/issues/213


k8s-ci-robot: Welcome @haiyanmeng! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/controller-tools'>kubernetes-sigs/controller-tools</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/controller-tools has [its own contribution guidelines](https://github.com/kubernetes-sigs/controller-tools/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/controller-tools/pull/234#" title="Author self-approved">haiyanmeng</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **pwittrock**
You can assign the PR to them by writing `/assign @pwittrock` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcontroller-tools).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/controller-tools/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["pwittrock"]} -->
DirectXMan12: PR & commit message need more description -- when I look back via `git commit`, I should be able to figure enough historical context w/o having to jump to github
DirectXMan12: This is a bit tricky -- we generally *don't* allow this, except with byte slices.  I think we should just special-case this to preserve the effect that output types are conformant with kubernetes API conventions.
haiyanmeng: @DirectXMan12 , limited the support only to `byte slices`. PTAL.
haiyanmeng: > can you add a test case for this in `pkg/crd/testdata`, please

Done. 
haiyanmeng: > can you add a test case for this in `pkg/crd/testdata`, please

This test was not happy with my change. However, the diff in https://travis-ci.org/kubernetes-sigs/controller-tools/jobs/553494323 is too complex to decode.

I also `$GOPATH/bin/controller-gen  crd paths=. output:dir=.` on the master branch, and it has diffs, which seems suprising:
```
haiyanmeng@haiyanmeng:testdata$ $GOPATH/bin/controller-gen  crd paths=. output:dir=.
haiyanmeng@haiyanmeng:testdata$ git diff
diff --git a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
index dbf4bd03..910e2665 100644
--- a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
+++ b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
@@ -4452,6 +4452,25 @@ spec:
                                   must match a node''s labels for the pod to be scheduled
                                   on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/'
                                 type: object
+                              overhead:
+                                additionalProperties:
+                                  type: string
+                                description: 'Overhead represents the resource overhead
+                                  associated with running a pod for a given RuntimeClass.
+                                  This field will be autopopulated at admission time
+                                  by the RuntimeClass admission controller. If the
+                                  RuntimeClass admission controller is enabled, overhead
+                                  must not be set in Pod create requests. The RuntimeClass
+                                  admission controller will reject Pod create requests
+                                  which have the overhead already set. If RuntimeClass
+                                  is configured and selected in the PodSpec, Overhead
+                                  will be set to the value defined in the corresponding
+                                  RuntimeClass, otherwise it will remain unset and
+                                  treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md
+                                  This field is alpha-level as of Kubernetes v1.16,
+                                  and is only honored by servers that enable the PodOverhead
+                                  feature.'
+                                type: object
                               preemptionPolicy:
                                 description: PreemptionPolicy is the Policy for preempting
                                   pods with lower priority. One of Never, PreemptLowerPriority.
```
DirectXMan12: That looks like we pulled in a newer version of kubernetes somehow.  Did you have `GO111MODULE=on` and were in the testdata directory?

Patch:
diff --git a/.travis.yml b/.travis.yml
index e9ac68db..6503b21c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ go_import_path: sigs.k8s.io/controller-tools
 
 install:
 - go get -u github.com/golang/dep/cmd/dep
-- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1
+- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0
 
 script:
 - GO111MODULE=on TRACE=1 ./test.sh
diff --git a/Gopkg.lock b/Gopkg.lock
index 4ac161c3..e60ad309 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -2,12 +2,12 @@
 
 
 [[projects]]
-  digest = "0:"
-  name = "github.com/gobuffalo/flect"
+  digest = "1:5b33a8ff87888677489ba612c8aa1bf06500b087f1397a4732b7a0298f6d5f8a"
+  name = "github.com/gobuffalo/envy"
   packages = ["."]
   pruneopts = "UT"
-  revision = "751c52157d8840ef1ebfeb65917fb9680ef51f24"
-  version = "v0.1.5"
+  revision = "fa0dfdc10b5366ce365b7d9d1755a03e4e797bc5"
+  version = "v1.6.15"
 
 [[projects]]
   digest = "1:4d02824a56d268f74a6b6fdd944b20b58a77c3d70e81008b3ee0c4f1a6777340"
@@ -64,6 +64,22 @@
   revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
   version = "v1.0.0"
 
+[[projects]]
+  digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b"
+  name = "github.com/joho/godotenv"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "23d116af351c84513e1946b527c88823e476be13"
+  version = "v1.3.0"
+
+[[projects]]
+  digest = "1:3804a3a02964db8e6db3e5e7960ac1c1a9b12835642dd4f4ac4e56c749ec73eb"
+  name = "github.com/markbates/inflect"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "24b83195037b3bc61fcda2d28b7b0518bce293b6"
+  version = "v1.0.4"
+
 [[projects]]
   digest = "1:e9c3bb68a6c9470302b8046d4647e0612a2ea6037b9c6a47de60c0a90db504f8"
   name = "github.com/onsi/ginkgo"
@@ -114,6 +130,18 @@
   revision = "90e289841c1ed79b7a598a7cd9959750cb5e89e2"
   version = "v1.5.0"
 
+[[projects]]
+  digest = "1:e09ada96a5a41deda4748b1659cc8953961799e798aea557257b56baee4ecaf3"
+  name = "github.com/rogpeppe/go-internal"
+  packages = [
+    "modfile",
+    "module",
+    "semver",
+  ]
+  pruneopts = "UT"
+  revision = "438578804ca6f31be148c27683afc419ce47c06e"
+  version = "v1.2.2"
+
 [[projects]]
   digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
   name = "github.com/spf13/cobra"
@@ -315,8 +343,8 @@
   analyzer-name = "dep"
   analyzer-version = 1
   input-imports = [
-    "github.com/gobuffalo/flect",
     "github.com/google/go-cmp/cmp",
+    "github.com/markbates/inflect",
     "github.com/onsi/ginkgo",
     "github.com/onsi/gomega",
     "github.com/onsi/gomega/gstruct",
diff --git a/cmd/controller-gen/main.go b/cmd/controller-gen/main.go
index 3c9e6583..63a6bb20 100644
--- a/cmd/controller-gen/main.go
+++ b/cmd/controller-gen/main.go
@@ -343,7 +343,7 @@ func runGenerators(rawOptions []string, printMarkersOnly bool) error {
 	rt.OutputRules = outputRules
 
 	if hadErrs := rt.Run(); hadErrs {
-		return fmt.Errorf("not all generators ran successfully")
+		return fmt.Errorf("not all generators ran succesfully")
 	}
 
 	return nil
diff --git a/go.mod b/go.mod
index 607ff2b3..70c34bde 100644
--- a/go.mod
+++ b/go.mod
@@ -3,24 +3,24 @@ module sigs.k8s.io/controller-tools
 go 1.12
 
 require (
-	github.com/gobuffalo/flect v0.1.5
+	github.com/gobuffalo/envy v1.6.15 // indirect
 	github.com/gogo/protobuf v1.2.1 // indirect
 	github.com/google/go-cmp v0.3.0
 	github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.6 // indirect
-	github.com/kr/pretty v0.1.0 // indirect
+	github.com/markbates/inflect v1.0.4
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.1 // indirect
 	github.com/onsi/ginkgo v1.8.0
 	github.com/onsi/gomega v1.5.0
+	github.com/rogpeppe/go-internal v1.2.2 // indirect
 	github.com/spf13/cobra v0.0.3
 	github.com/spf13/pflag v1.0.3 // indirect
 	golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 // indirect
 	golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 // indirect
 	golang.org/x/text v0.3.2 // indirect
 	golang.org/x/tools v0.0.0-20190501045030-23463209683d
-	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.2.2 // indirect
 	k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
diff --git a/go.sum b/go.sum
index 9fb189b0..e88da56c 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo=
-github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
+github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
+github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
@@ -17,6 +18,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -26,6 +29,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
+github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
@@ -37,11 +42,15 @@ github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -68,6 +77,7 @@ golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBn
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
diff --git a/pkg/crd/schema.go b/pkg/crd/schema.go
index 161497a4..a68a2841 100644
--- a/pkg/crd/schema.go
+++ b/pkg/crd/schema.go
@@ -291,6 +291,12 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *v1beta1.JSONSchemaPr
 		valSchema = localNamedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
 	case *ast.SelectorExpr:
 		valSchema = namedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
+	case *ast.ArrayType:
+		valSchema = arrayToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
+		if valSchema.Type == "array" && valSchema.Items.Schema.Type != "string" {
+			ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
+			return &v1beta1.JSONSchemaProps{}
+		}
 	default:
 		ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
 		return &v1beta1.JSONSchemaProps{}
diff --git a/pkg/crd/spec.go b/pkg/crd/spec.go
index 94fa2220..a1cbfb25 100644
--- a/pkg/crd/spec.go
+++ b/pkg/crd/spec.go
@@ -19,8 +19,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/gobuffalo/flect"
-
+	"github.com/markbates/inflect"
 	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -54,7 +53,7 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind) {
 		packages = append(packages, pkg)
 	}
 
-	defaultPlural := flect.Pluralize(strings.ToLower(groupKind.Kind))
+	defaultPlural := inflect.NewDefaultRuleset().Pluralize(strings.ToLower(groupKind.Kind))
 	crd := apiext.CustomResourceDefinition{
 		TypeMeta: metav1.TypeMeta{
 			APIVersion: apiext.SchemeGroupVersion.String(),
diff --git a/pkg/genall/output.go b/pkg/genall/output.go
index 9e8db36c..025b8e5a 100644
--- a/pkg/genall/output.go
+++ b/pkg/genall/output.go
@@ -132,7 +132,7 @@ func (o OutputArtifacts) Open(pkg *loader.Package, itemPath string) (io.WriteClo
 	}
 
 	if len(pkg.CompiledGoFiles) == 0 {
-		return nil, fmt.Errorf("cannot output to a package with no path on disk")
+		return nil, fmt.Errorf("cannot ouput to a package with no path on disk")
 	}
 	outDir := filepath.Dir(pkg.CompiledGoFiles[0])
 	outPath := filepath.Join(outDir, itemPath)
diff --git a/pkg/loader/refs.go b/pkg/loader/refs.go
index 9e2989cf..13b53cef 100644
--- a/pkg/loader/refs.go
+++ b/pkg/loader/refs.go
@@ -187,7 +187,6 @@ func allReferencedPackages(pkg *Package, filterNodes NodeFilter) []*Package {
 type TypeChecker struct {
 	checkedPackages map[*Package]struct{}
 	filterNodes     NodeFilter
-	sync.Mutex
 }
 
 // Check type-checks the given package and all packages referenced
@@ -225,10 +224,7 @@ func (c *TypeChecker) check(root *Package) {
 	root.Lock()
 	defer root.Unlock()
 
-	c.Lock()
-	_, ok := c.checkedPackages[root]
-	c.Unlock()
-	if ok {
+	if _, ok := c.checkedPackages[root]; ok {
 		return
 	}
 
@@ -248,7 +244,5 @@ func (c *TypeChecker) check(root *Package) {
 	// ...then, we can safely type-check ourself
 	root.NeedTypesInfo()
 
-	c.Lock()
-	defer c.Unlock()
 	c.checkedPackages[root] = struct{}{}
 }
diff --git a/pkg/rbac/parser.go b/pkg/rbac/parser.go
index 97128603..c6391b68 100644
--- a/pkg/rbac/parser.go
+++ b/pkg/rbac/parser.go
@@ -23,9 +23,6 @@ limitations under the License.
 package rbac
 
 import (
-	"sort"
-	"strings"
-
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
@@ -47,57 +44,8 @@ type Rule struct {
 	URLs      []string `marker:"urls,optional"`
 }
 
-// ruleKey represents the resources and non-resources a Rule applies.
-type ruleKey struct {
-	Groups    string
-	Resources string
-	URLs      string
-}
-
-// key normalizes the Rule and returns a ruleKey object.
-func (r *Rule) key() ruleKey {
-	r.normalize()
-	return ruleKey{
-		Groups:    strings.Join(r.Groups, "&"),
-		Resources: strings.Join(r.Resources, "&"),
-		URLs:      strings.Join(r.URLs, "&"),
-	}
-}
-
-// addVerbs adds new verbs into a Rule.
-// The duplicates in `r.Verbs` will be removed, and then `r.Verbs` will be sorted.
-func (r *Rule) addVerbs(verbs []string) {
-	r.Verbs = removeDupAndSort(append(r.Verbs, verbs...))
-}
-
-// normalize removes duplicates from each field of a Rule, and sorts each field.
-func (r *Rule) normalize() {
-	r.Groups = removeDupAndSort(r.Groups)
-	r.Resources = removeDupAndSort(r.Resources)
-	r.Verbs = removeDupAndSort(r.Verbs)
-	r.URLs = removeDupAndSort(r.URLs)
-}
-
-// removeDupAndSort removes duplicates in strs, sorts the items, and returns a
-// new slice of strings.
-func removeDupAndSort(strs []string) []string {
-	set := make(map[string]bool)
-	for _, str := range strs {
-		if _, ok := set[str]; !ok {
-			set[str] = true
-		}
-	}
-
-	var result []string
-	for str := range set {
-		result = append(result, str)
-	}
-	sort.Strings(result)
-	return result
-}
-
 // ToRule converts this rule to its Kubernetes API form.
-func (r *Rule) ToRule() rbacv1.PolicyRule {
+func (r Rule) ToRule() rbacv1.PolicyRule {
 	// fix the group names first, since letting people type "core" is nice
 	for i, group := range r.Groups {
 		if group == "core" {
@@ -120,33 +68,20 @@ type Generator struct {
 func (Generator) RegisterMarkers(into *markers.Registry) error {
 	return into.Register(RuleDefinition)
 }
-
 func (g Generator) Generate(ctx *genall.GenerationContext) error {
-	rules := make(map[ruleKey]*Rule)
+	var rules []rbacv1.PolicyRule
 	for _, root := range ctx.Roots {
 		markerSet, err := markers.PackageMarkers(ctx.Collector, root)
 		if err != nil {
 			root.AddError(err)
 		}
 
-		for _, markerValue := range markerSet[RuleDefinition.Name] {
-			rule := markerValue.(Rule)
-			key := rule.key()
-			if _, ok := rules[key]; !ok {
-				rules[key] = &rule
-				continue
-			}
-			rules[key].addVerbs(rule.Verbs)
+		for _, rule := range markerSet[RuleDefinition.Name] {
+			rules = append(rules, rule.(Rule).ToRule())
 		}
 	}
 
-	var policyRules []rbacv1.PolicyRule
-	for _, rule := range rules {
-		policyRules = append(policyRules, rule.ToRule())
-
-	}
-
-	if len(policyRules) == 0 {
+	if len(rules) == 0 {
 		return nil
 	}
 
@@ -158,7 +93,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
 		ObjectMeta: metav1.ObjectMeta{
 			Name: g.RoleName,
 		},
-		Rules: policyRules,
+		Rules: rules,
 	}); err != nil {
 		return err
 	}
diff --git a/pkg/typescaffold/resource.go b/pkg/typescaffold/resource.go
index e4c2388a..cd982e00 100644
--- a/pkg/typescaffold/resource.go
+++ b/pkg/typescaffold/resource.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/gobuffalo/flect"
+	"github.com/markbates/inflect"
 )
 
 // Resource contains the information required to scaffold files for a resource.
@@ -41,12 +41,13 @@ func (r *Resource) Validate() error {
 		return fmt.Errorf("kind cannot be empty")
 	}
 
+	rs := inflect.NewDefaultRuleset()
 	if len(r.Resource) == 0 {
-		r.Resource = flect.Pluralize(strings.ToLower(r.Kind))
+		r.Resource = rs.Pluralize(strings.ToLower(r.Kind))
 	}
 
-	if r.Kind != flect.Pascalize(r.Kind) {
-		return fmt.Errorf("Kind must be camelcase (expected %s was %s)", flect.Pascalize(r.Kind), r.Kind)
+	if r.Kind != inflect.Camelize(r.Kind) {
+		return fmt.Errorf("Kind must be camelcase (expected %s was %s)", inflect.Camelize(r.Kind), r.Kind)
 	}
 
 	return nil


Test patch:
diff --git a/pkg/crd/testdata/cronjob_types.go b/pkg/crd/testdata/cronjob_types.go
index 208b3cdd..ec4faa99 100644
--- a/pkg/crd/testdata/cronjob_types.go
+++ b/pkg/crd/testdata/cronjob_types.go
@@ -78,6 +78,12 @@ type CronJobSpec struct {
 	// This is a pointer to distinguish between explicit zero and not specified.
 	// +optional
 	FailedJobsHistoryLimit *int32 `json:"failedJobsHistoryLimit,omitempty"`
+
+	// This tests byte slices are allowed as map values.
+	ByteSliceData map[string][]byte `json:"byteSliceData,omitempty"`
+
+	// This tests string slices are allowed as map values.
+	StringSliceData map[string][]string `json:"stringSliceData,omitempty"`
 }
 
 // use an explicit type marker to verify that apply-first markers generate properly
@@ -85,8 +91,13 @@ type CronJobSpec struct {
 // +kubebuilder:validation:Type=string
 // TotallyABool is a bool that serializes as a string.
 type TotallyABool bool
+
 func (t TotallyABool) MarshalJSON() ([]byte, error) {
-	if t { return []byte(`"true"`), nil } else { return []byte(`"false"`), nil}
+	if t {
+		return []byte(`"true"`), nil
+	} else {
+		return []byte(`"false"`), nil
+	}
 }
 func (t *TotallyABool) UnmarshalJSON(in []byte) error {
 	switch string(in) {
@@ -100,7 +111,6 @@ func (t *TotallyABool) UnmarshalJSON(in []byte) error {
 	}
 }
 
-
 // ConcurrencyPolicy describes how the job will be handled.
 // Only one of the following concurrent policies may be specified.
 // If none of the following policies is specified, the default one
diff --git a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
index dbf4bd03..dc5d985e 100644
--- a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
+++ b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml
@@ -427,6 +427,12 @@ spec:
                 description: This tests byte slice schema generation.
                 format: byte
                 type: string
+              byteSliceData:
+                additionalProperties:
+                  format: byte
+                  type: string
+                description: This tests byte slices are allowed as map values.
+                type: object
               canBeNull:
                 description: This tests that nullable works correctly
                 nullable: true
@@ -6189,6 +6195,13 @@ spec:
                   will be counted as failed ones.
                 format: int64
                 type: integer
+              stringSliceData:
+                additionalProperties:
+                  items:
+                    type: string
+                  type: array
+                description: This tests string slices are allowed as map values.
+                type: object
               successfulJobsHistoryLimit:
                 description: The number of successful finished jobs to retain. This
                   is a pointer to distinguish between explicit zero and not specified.