Skip to content

Commit

Permalink
Merge pull request #5130 from KnVerey/fn-framework-example
Browse files Browse the repository at this point in the history
Add a rich example of fn framework for abstraction
  • Loading branch information
k8s-ci-robot committed Apr 14, 2023
2 parents d3184da + 38d5bf8 commit 315ed56
Show file tree
Hide file tree
Showing 28 changed files with 1,469 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ test-unit-all: \
# This target is used by our Github Actions CI to run unit tests for all non-plugin modules in multiple GOOS environments.
.PHONY: test-unit-non-plugin
test-unit-non-plugin:
./hack/for-each-module.sh "make test" "./plugin/*" 15
./hack/for-each-module.sh "make test" "./plugin/*" 16

.PHONY: build-non-plugin-all
build-non-plugin-all:
./hack/for-each-module.sh "make build" "./plugin/*" 15
./hack/for-each-module.sh "make build" "./plugin/*" 16

.PHONY: test-unit-kustomize-plugins
test-unit-kustomize-plugins:
Expand Down
25 changes: 25 additions & 0 deletions functions/examples/fn-framework-application/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export KUSTOMIZE_ROOT ?= $(shell pwd | sed -E 's|(.*\/kustomize)/(.*)|\1|')
include $(KUSTOMIZE_ROOT)/Makefile-modules.mk

CONTROLLER_GEN_VERSION=v0.11.3

generate: $(MYGOBIN)/controller-gen $(MYGOBIN)/embedmd
go generate ./...
embedmd -w README.md

build: generate
go build -v -o $(MYGOBIN)/app-fn cmd/main.go

$(MYGOBIN)/controller-gen:
go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION)

$(MYGOBIN)/embedmd:
go install github.com/campoy/embedmd@v1.0.0

.PHONY: example
example: build
$(MYGOBIN)/app-fn pkg/exampleapp/testdata/success/basic/config.yaml


test: generate
go test -v -timeout 45m -cover ./...
132 changes: 132 additions & 0 deletions functions/examples/fn-framework-application/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
## Kyaml Functions Framework Example: Application Custom Resource

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.

[embedmd]:# (pkg/exampleapp/v1alpha1/testdata/success/basic/config.yaml)
```yaml
apiVersion: platform.example.com/v1alpha1
kind: ExampleApp
metadata:
name: simple-app-sample
env: production
workloads:
webWorkers:
- name: web-worker
domains:
- example.com
jobWorkers:
- name: job-worker
replicas: 10
resources: medium
queues:
- high
- medium
- low
- name: job-worker-2
replicas: 5
queues:
- bg2
datastores:
postgresInstance: simple-app-sample-postgres
```

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.

[embedmd]:# (pkg/exampleapp/v1alpha1/testdata/success/overrides/config.yaml)
```yaml
apiVersion: platform.example.com/v1alpha1
kind: ExampleApp
metadata:
name: simple-app-sample
env: production
workloads:
webWorkers:
- name: web-worker
domains:
- first.example.com
- name: web-worker-no-sidecar
domains:
- second.example.com

overrides:
additionalResources:
- custom-configmap.yaml
resourcePatches:
- web-worker-sidecar.yaml
containerPatches:
- custom-app-env.yaml
```

## Implementation walkthrough

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`).

[embedmd]:# (pkg/dispatcher/dispatcher.go go /.*VersionedAPIProcessor.*/ /}}/)
```go
p := framework.VersionedAPIProcessor{FilterProvider: framework.GVKFilterMap{
"ExampleApp": map[string]kio.Filter{
"platform.example.com/v1alpha1": &v1alpha1.ExampleApp{},
},
}}
```


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`.

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.

[embedmd]:# (pkg/exampleapp/v1alpha1/types.go go /.*type ExampleApp.*/ /}/)
```go
type ExampleApp struct {
// Embedding these structs is required to use controller-gen to produce the CRD
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`

// +kubebuilder:validation:Enum=production;staging;development
Env string `json:"env" yaml:"env"`

// +optional
AppImage string `json:"appImage" yaml:"appImage"`

Workloads Workloads `json:"workloads" yaml:"workloads"`

// +optional
Datastores Datastores `json:"datastores,omitempty" yaml:"datastores,omitempty"`

// +optional
Overrides Overrides `json:"overrides,omitempty" yaml:"overrides,omitempty"`
}
```

[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Filter.*/ /error\) {/)
```go
func (a ExampleApp) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
```
[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Schema.*/ /error\) {/)
```go
func (a *ExampleApp) Schema() (*spec.Schema, error) {
```
[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Validate.*/ /error {/)
```go
func (a *ExampleApp) Validate() error {
```
[embedmd]:# (pkg/exampleapp/v1alpha1/processing.go go /.*Default.*/ /error {/)
```go
func (a *ExampleApp) Default() error {
```
## Running the Example
There are three ways to try this out:
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).
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`.
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`.
10 changes: 10 additions & 0 deletions functions/examples/fn-framework-application/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2023 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package main

import "sigs.k8s.io/kustomize/functions/examples/fn-framework-application/pkg/dispatcher"

func main() {
_ = dispatcher.NewCommand().Execute()
}
50 changes: 50 additions & 0 deletions functions/examples/fn-framework-application/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module sigs.k8s.io/kustomize/functions/examples/fn-framework-application

go 1.19

require (
github.com/spf13/cobra v1.4.0
k8s.io/apimachinery v0.27.0
k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a
sigs.k8s.io/kustomize/kyaml v0.14.1
)

require (
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

0 comments on commit 315ed56

Please sign in to comment.