|
| 1 | +## Kyaml Functions Framework Example: Application Custom Resource |
| 2 | + |
| 3 | +This is a moderate-complexity example of a KRM function built using the [KRM Functions Framework package](https://pkg.go.dev/sigs.k8s.io/kustomize/kyaml/fn/framework). It demonstrates how to write a function that implements a custom resource (CR) representing an abstract application. |
| 4 | + |
| 5 | +[embedmd]:# (pkg/exampleapp/v1alpha1/testdata/success/basic/config.yaml) |
| 6 | +```yaml |
| 7 | +apiVersion: platform.example.com/v1alpha1 |
| 8 | +kind: ExampleApp |
| 9 | +metadata: |
| 10 | + name: simple-app-sample |
| 11 | +env: production |
| 12 | +workloads: |
| 13 | + webWorkers: |
| 14 | + - name: web-worker |
| 15 | + domains: |
| 16 | + - example.com |
| 17 | + jobWorkers: |
| 18 | + - name: job-worker |
| 19 | + replicas: 10 |
| 20 | + resources: medium |
| 21 | + queues: |
| 22 | + - high |
| 23 | + - medium |
| 24 | + - low |
| 25 | + - name: job-worker-2 |
| 26 | + replicas: 5 |
| 27 | + queues: |
| 28 | + - bg2 |
| 29 | +datastores: |
| 30 | + postgresInstance: simple-app-sample-postgres |
| 31 | +``` |
| 32 | +
|
| 33 | +It also demonstrates the pattern of having the CR accept patches, allowing the user to customize the final result beyond the fields the CR exposes. |
| 34 | +
|
| 35 | +[embedmd]:# (pkg/exampleapp/v1alpha1/testdata/success/overrides/config.yaml) |
| 36 | +```yaml |
| 37 | +apiVersion: platform.example.com/v1alpha1 |
| 38 | +kind: ExampleApp |
| 39 | +metadata: |
| 40 | + name: simple-app-sample |
| 41 | +env: production |
| 42 | +workloads: |
| 43 | + webWorkers: |
| 44 | + - name: web-worker |
| 45 | + domains: |
| 46 | + - first.example.com |
| 47 | + - name: web-worker-no-sidecar |
| 48 | + domains: |
| 49 | + - second.example.com |
| 50 | + |
| 51 | +overrides: |
| 52 | + additionalResources: |
| 53 | + - custom-configmap.yaml |
| 54 | + resourcePatches: |
| 55 | + - web-worker-sidecar.yaml |
| 56 | + containerPatches: |
| 57 | + - custom-app-env.yaml |
| 58 | +``` |
| 59 | +
|
| 60 | +## Implementation walkthrough |
| 61 | +
|
| 62 | +The entrypoint for the function is [cmd/main.go](cmd/main.go), which invokes a ["dispatcher"](pkg/dispatcher/dispatcher.go) that determines which `Filter` implements the resource passed in. The dispatcher pattern allows a single function binary to handle multiple CRs, and is also useful for evolving a single CR over time (e.g. handle `ExampleApp` API versions `example.com/v1beta1` and `example.com/v1`). |
| 63 | + |
| 64 | +[embedmd]:# (pkg/dispatcher/dispatcher.go go /.*VersionedAPIProcessor.*/ /}}/) |
| 65 | +```go |
| 66 | + p := framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{ |
| 67 | + "ExampleApp": map[string]kio.Filter{ |
| 68 | + "platform.example.com/v1alpha1": &v1alpha1.ExampleApp{}, |
| 69 | + }, |
| 70 | + }} |
| 71 | +``` |
| 72 | + |
| 73 | + |
| 74 | +The ExampleApp type is defined in [pkg/exampleapp/v1alpha1/types.go](pkg/exampleapp/v1alpha1/types.go). It is responsible for implementing the logic of the CR, most of which is done by implementing the `kyaml.Filter` interface in [pkg/exampleapp/v1alpha1/processing.go](pkg/exampleapp/v1alpha1/processing.go). Internally, the filter function mostly builds up and executes a `framework.TemplateProcessor`. |
| 75 | + |
| 76 | +The ExampleApp type is annotated with [kubebuilder markers](https://book.kubebuilder.io/reference/markers/crd-validation.html), and a Go generator uses those to create the CRD YAML in [pkg/exampleapp/v1alpha1/platform.example.com_exampleapps.yaml](pkg/exampleapp/v1alpha1/platform.example.com_exampleapps.yaml). The CR then implements `framework.ValidationSchemaProvider`, which causes the CRD to be used for validation. It also implements `framework.Validator` to add custom validations and `framework.Defaulter` to add defaulting. |
| 77 | + |
| 78 | +[embedmd]:# (pkg/exampleapp/v1alpha1/types.go go /.*type ExampleApp.*/ /}/) |
| 79 | +```go |
| 80 | +type ExampleApp struct { |
| 81 | + // Embedding these structs is required to use controller-gen to produce the CRD |
| 82 | + metav1.TypeMeta `json:",inline"` |
| 83 | + metav1.ObjectMeta `json:"metadata"` |
| 84 | + |
| 85 | + // +kubebuilder:validation:Enum=production;staging;development |
| 86 | + Env string `json:"env" yaml:"env"` |
| 87 | + |
| 88 | + // +optional |
| 89 | + AppImage string `json:"appImage" yaml:"appImage"` |
| 90 | + |
| 91 | + Workloads Workloads `json:"workloads" yaml:"workloads"` |
| 92 | + |
| 93 | + // +optional |
| 94 | + Datastores Datastores `json:"datastores,omitempty" yaml:"datastores,omitempty"` |
| 95 | + |
| 96 | + // +optional |
| 97 | + Overrides Overrides `json:"overrides,omitempty" yaml:"overrides,omitempty"` |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Filter.*/ /error\) {/) |
| 102 | +```go |
| 103 | +func (a ExampleApp) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) { |
| 104 | +``` |
| 105 | +
|
| 106 | +[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Schema.*/ /error\) {/) |
| 107 | +```go |
| 108 | +func (a *ExampleApp) Schema() (*spec.Schema, error) { |
| 109 | +``` |
| 110 | +
|
| 111 | +[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Validate.*/ /error {/) |
| 112 | +```go |
| 113 | +func (a *ExampleApp) Validate() error { |
| 114 | +``` |
| 115 | +
|
| 116 | +[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Default.*/ /error {/) |
| 117 | +```go |
| 118 | +func (a *ExampleApp) Default() error { |
| 119 | +``` |
| 120 | +
|
| 121 | +
|
| 122 | +## Running the Example |
| 123 | +
|
| 124 | +There are three ways to try this out: |
| 125 | +
|
| 126 | +A. Run `make example` in the root of the example to run the function with the test data in [pkg/exampleapp/v1alpha1/testdata/success/basic](pkg/exampleapp/v1alpha1/testdata/success/basic). |
| 127 | +
|
| 128 | +B. Run `go run cmd/main.go [FILE]` in the root of the example. Try it with the test input from one of the cases in [pkg/exampleapp/v1alpha1/testdata/success](pkg/exampleapp/v1alpha1/testdata/success). For example: `go run cmd/main.go pkg/exampleapp/v1alpha1/testdata/success/basic/config.yaml`. |
| 129 | +
|
| 130 | +C. Build the binary with `make build`, then run it with `app-fn [FILE]`. Try it with the test input from one of the cases in [pkg/exampleapp/v1alpha1/testdata/success](pkg/exampleapp/v1alpha1/testdata/success). For example: `app-fn pkg/exampleapp/v1alpha1/testdata/success/basic/config.yaml`. |
| 131 | +
|
| 132 | +
|
0 commit comments