|
| 1 | +# Code generation |
| 2 | + |
| 3 | +In order to keep the code for all the service controllers consistent, we will |
| 4 | +use a strategy of generating the custom resource definitions and controller |
| 5 | +code stubs for new AWS services. |
| 6 | + |
| 7 | +To generate the CRDs and controller stub code, we investigated a number of |
| 8 | +options: |
| 9 | + |
| 10 | +* home-grown custom code generator |
| 11 | +* [kudo](https://kudo.dev) |
| 12 | +* [kubebuilder](github.com/kubernetes-sigs/kubebuilder) |
| 13 | +* a hybrid custom code generator + sigs.kubernetes.io/controller-tools (CR) |
| 14 | + |
| 15 | +The original AWS Service Operator used a [custom-built generator][1] that |
| 16 | +processed [YAML manifests][2] describing the AWS service and used |
| 17 | +[templates][3] to [generate CRDs][4], the [controller code][5] itself and the |
| 18 | +[Go types][6] that represent the CRDs in memory. It's worth noting that the |
| 19 | +CRDs *and* the controller code that was generated by the original ASO was very |
| 20 | +tightly coupled to CloudFormation. In fact, the CRDs for individual AWS |
| 21 | +services like S3 or RDS were thin wrappers around CloudFormation stacks that |
| 22 | +described the object being operated upon. |
| 23 | + |
| 24 | +[1]: https://github.com/amazon-archives/aws-service-operator/tree/master/code-generation |
| 25 | +[2]: https://github.com/amazon-archives/aws-service-operator/tree/master/models |
| 26 | +[3]: https://github.com/amazon-archives/aws-service-operator/tree/master/code-generation/pkg/codegen/assets |
| 27 | +[4]: https://github.com/amazon-archives/aws-service-operator/blob/b4befd62322a57ac78aa39ea08771fc32912592a/code-generation/pkg/codegen/assets/aws-service-operator.yaml.templ#L13-L31 |
| 28 | +[5]: https://github.com/amazon-archives/aws-service-operator/blob/master/code-generation/pkg/codegen/assets/operator.go.templ |
| 29 | +[6]: https://github.com/amazon-archives/aws-service-operator/blob/master/code-generation/pkg/codegen/assets/types.go.templ |
| 30 | + |
| 31 | +kudo is a platform for building Kubernetes Operators. It stores state in its |
| 32 | +own kudo.dev CRDs and allows users to define "plans" for a deployed application |
| 33 | +to deploy itself. We determined that kudo was not a particularly good fit for |
| 34 | +ASO for a couple reasons. First, we needed a way to generate CRDs in several |
| 35 | +API groups (s3.aws.com and iam.aws.com for example) and the ASO controller code |
| 36 | +isn't deploying an "application" that needs to have a controlled deployment |
| 37 | +plan. Instead, ASO is a controller that facilitates creation and management of |
| 38 | +various AWS service objects using Kubernetes CRD instances. |
| 39 | + |
| 40 | +kubebuilder is the recommended upstream tool for generating CRDs and controller |
| 41 | +stub code. It is a Go binary that creates the scaffolding for CRDs and |
| 42 | +controller Go code. Unfortunately, it doesn't really handle multiple API groups |
| 43 | +in the same source code repository, and we need that for ASO. For example, we |
| 44 | +will have an `s3.service-operator.aws` and an `iam.service-operator.aws` API |
| 45 | +group with CRDs representing those different API objects and interfaces. |
| 46 | + |
| 47 | +That said, much of kubebuilder simply sets up some templates and then runs the |
| 48 | +`controller-gen` binary from the sigs.kubernetes.io/controller-tools repository |
| 49 | +to generate Go code. |
| 50 | + |
| 51 | +Our final option was to build a hybrid custom code generator that used |
| 52 | +controller-runtime under the hood but allowed us to generate controller stub |
| 53 | +code for multiple API groups and place generated code [in directories][7] that |
| 54 | +represented Go best practices. This option gives us the flexibility to generate |
| 55 | +the files and content for multiple API groups but still stay within the |
| 56 | +recommended guardrails of the upstream Kubernetes community. |
| 57 | + |
| 58 | +[7]: https://github.com/kubernetes-sigs/kubebuilder/issues/1268 |
| 59 | + |
| 60 | +## Hybrid custom+controller-runtime proposal |
| 61 | + |
| 62 | + |
| 63 | + |
| 64 | +This approach uses multiple phases of code generation. |
| 65 | + |
| 66 | +The first code generation phase consumes model information from a canonical |
| 67 | +source of truth about an AWS service and the objects and interfaces that |
| 68 | +service exposes and generates one or more files containing code that exposes |
| 69 | +Go types for those objects. These "type files" should be annotated with the |
| 70 | +marker and comments that will allow the core code generators and controller-gen |
| 71 | +to do its work. |
| 72 | + |
| 73 | +Once we have generated the type files, we need to generate basic scaffolding |
| 74 | +for consumers of those types. The upstream code-generator project contains |
| 75 | +generators for this scaffolding. We should be able to make a modified version |
| 76 | +of the upstream [`generate-groups.sh`][8] script to generate all the defaults, |
| 77 | +deepcopy, informers, listers and clientset code. |
| 78 | + |
| 79 | +[8]: https://github.com/kubernetes/code-generator/blob/6b257a9d6f461b5e15dc2f0d13e29731a5b5255a/generate-groups.sh |
| 80 | + |
| 81 | +Next, we will need to generate some skeleton code for the reconciling |
| 82 | +controller handling the new API along with the YAML files representing the CRDs |
| 83 | +that will get loaded into kube-apiserver and the YAML files for RBAC and |
| 84 | +OpenAPI v3 schemas for the CRDs. This is where the [`controller-gen`][9] tool |
| 85 | +from the sigs.kubernetes.io/controller-tools project will come in handy. |
| 86 | + |
| 87 | +[9]: https://github.com/kubernetes-sigs/controller-tools/blob/a5fa7b956b85a6e792bc7086fedf7107d62452b1/cmd/controller-gen/main.go |
| 88 | + |
| 89 | +We also want to generate some stub code for a new reconciling controller into |
| 90 | +the primary aws-service-operator binary. |
| 91 | + |
| 92 | +When we run `make generate $SERVICE $VERSION`, we should end up with a directory |
| 93 | +structure like this: |
| 94 | + |
| 95 | +``` |
| 96 | +/cmd |
| 97 | + /aws-service-operator |
| 98 | + main.go |
| 99 | +/crds |
| 100 | + /$SERVICE |
| 101 | + crds.yaml |
| 102 | + rbac.yaml |
| 103 | +/pkg |
| 104 | + /apis |
| 105 | + /$SERVICE |
| 106 | + /$VERSION |
| 107 | + doc.go |
| 108 | + register.go |
| 109 | + types.go |
| 110 | + deepcopy.go |
| 111 | + defaults.go |
| 112 | + /client |
| 113 | + /clientset |
| 114 | + /versioned |
| 115 | + /fake |
| 116 | + ... |
| 117 | + /scheme |
| 118 | + ... |
| 119 | + /typed |
| 120 | + /$SERVICE |
| 121 | + /$VERSION |
| 122 | + … |
| 123 | + /controllers |
| 124 | + /$SERVICE |
| 125 | + controller.go |
| 126 | + /informers |
| 127 | + /externalversions |
| 128 | + /$SERVICE |
| 129 | + /$VERSION |
| 130 | + ... |
| 131 | + /internalinterfaces |
| 132 | + /listers |
| 133 | + /$SERVICE |
| 134 | + /$VERSION |
| 135 | + ... |
| 136 | +``` |
0 commit comments