Skip to content

Commit 59df835

Browse files
committed
A guide for function SDK. It focuses more on explaining the fn editing patterns
1 parent 1a2b640 commit 59df835

4 files changed

Lines changed: 190 additions & 2 deletions

File tree

scripts/generate_site_sidebar/sidebar_template.md.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@
6868
- [Namespace Provisioning UI](guides/namespace-provisioning-ui.md)
6969
- [Variant Constructor Pattern](guides/variant-constructor-pattern.md)
7070
- [Value Propagation Pattern](guides/value-propagation.md)
71+
- [Effective Go KRM functions](guides/effective-go-krm-function.md)
7172
- [FAQ](faq/)
7273
- [Contact](contact/)

site/book/05-developing-functions/02-developing-in-Go.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ You can develop a KRM function in Go using [the kpt function SDK].
1818
In this quickstart, we will write a function that adds an annotation
1919
`config.kubernetes.io/managed-by=kpt` to all `Deployment` resources.
2020

21+
This quickstart takes 15 minutes to finish.
22+
2123
### Initialize your project
2224

2325
We start from a "get-started" package which contains a `main.go` file with some scaffolding code.
@@ -126,11 +128,13 @@ kpt fn eval ./data --image ${FN_CONTAINER_REGISTRY}/${FUNCTION_NAME}:${TAG}
126128

127129
## Next Steps
128130

129-
- See other [go doc examples] to use KubeObject.
131+
- Read [effective go KRM function]
132+
- See the [go doc examples] to use KubeObject.
130133
- To contribute to KRM catalog functions, please follow the [contributor guide](https://github.com/GoogleContainerTools/kpt-functions-catalog/blob/master/CONTRIBUTING.md)
131134

132135
[the kpt function SDK]: https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn
133136
[go doc examples]: https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/examples
134137
[`fn`]: https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn
135138
[`ResourceList`]: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
136139
[`unstructured.Unstrucutred`]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
140+
[effective go KRM function]: guides/effective-go-krm-function.md

site/guides/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
- [Namespace Provisioning CLI](guides/namespace-provisioning-cli.md)
88
- [Namespace Provisioning UI](guides/namespace-provisioning-ui.md)
99
- [Variant Constructor Pattern](guides/variant-constructor-pattern.md)
10-
- [Value Propagation Pattern](guides/value-propagation.md)
10+
- [Value Propagation Pattern](guides/value-propagation.md)
11+
- [Effective Go KRM functions](guides/effective-go-krm-function.md)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Effective GO KRM functions
2+
3+
This guide gives some tips on how to write your own KRM function to mutate or/and validate a package.
4+
5+
This guide is for kpt beginners who are familiar with [Golang] and container runtime (e.g. Docker)
6+
7+
Suggested prerequisite reading: [Developing in Go]
8+
9+
## Prerequisites
10+
11+
- [Install kpt]
12+
- [Install Docker]
13+
14+
## Setup
15+
16+
<!--- TODO: Use scaffolding to generate the get-started package --->
17+
18+
We start from a "get-started-simple" package which contains a `main.go` file with some scaffolding code.
19+
20+
21+
```shell
22+
# Set your KRM function name.
23+
export FUNCTION_NAME=<YOUR FUNCTION NAME>
24+
export GOPATH=$(go env GOPATH)
25+
export FUNCTION_PATH=github.com/<YOUR USERNAME>
26+
27+
# Create and direct to your Go working directory
28+
mkdir -p $GOPATH/src/${FUNCTION_PATH} && cd $GOPATH/src/${FUNCTION_PATH}
29+
30+
# Get the "get-started" package.
31+
kpt pkg get https://github.com/GoogleContainerTools/kpt-functions-sdk.git/go/get-started-simple@master ${FUNCTION_NAME}
32+
33+
cd ${FUNCTION_NAME}
34+
35+
# Initialize Go module and install the kpt KRM function SDK.
36+
go mod init && go mod tidy -compat=1.17
37+
```
38+
39+
## Write your KRM function code in Go
40+
41+
Let's write the `FunctionX` and `Run` method.
42+
43+
`FunctionX` implements the [`Runner`] interface
44+
that can process the input KRM resources as [`fn.ResourceList`], it initializes `fn.KubeObject` to hold the KRM resources,
45+
so that you can call [`fn.KubeObject` and `fn.SubObject`] methods. Then it converts the modified `fn.KubeObjects` to KRM resources.
46+
47+
### Add your configures in `FunctionX`
48+
49+
If you need to use configurable variables, you can define them as `FunctionX` fields.
50+
Otherwise you can skip this step and move to next.
51+
```go
52+
type FunctionX struct {
53+
FnConfigBool bool
54+
FnConfigInt int
55+
FnConfigFoo string
56+
}
57+
```
58+
59+
For example, define a `SetImage` and add two variables to compare-and-swap the image value.
60+
```go
61+
type SetImage struct {
62+
OldImage string // Existing image
63+
NewImage string // New image to replace
64+
}
65+
```
66+
67+
The `SetImage` is a KRM resource. It should be passed from the input as:
68+
```yaml
69+
apiVersion: config.kubernetes.io/v1
70+
kind: ResourceList
71+
functionConfig:
72+
apiVersion: fn.kpt.dev/v1alpha1
73+
kind: SetImage
74+
metadata:
75+
name: try-out
76+
oldImage: example
77+
newImage: <YOUR NEW IMAGE NAME>
78+
items:
79+
...
80+
```
81+
<!--- TODO: we should not require users to understand and provide the input ResourceList.
82+
We should build a test infra that users only provide the KRM resources--->
83+
84+
### Add main logic in `Run`
85+
86+
The SDK will initialize a slice of `*fn.KubeObject` to hold your KRM resources. You will need to pass the
87+
KRM resources from the input in `items` fields:
88+
```yaml
89+
apiVersion: config.kubernetes.io/v1
90+
kind: ResourceList
91+
functionConfig:
92+
...
93+
items:
94+
- apiVersion: apps/v1
95+
kind: Deployment
96+
...
97+
- apiVersion: v1
98+
kind: ConfigMap
99+
data:
100+
owner: kpt
101+
...
102+
```
103+
<!--- TODO: we should not require users to understand and provide the input ResourceList.
104+
We should build a test infra that users only provide the KRM resources--->
105+
106+
#### Select KRM resources
107+
The `fn.KubeObjects` is a slice of `*fn.KubeObject`, that you can apply some select logic to easily choose
108+
the target KRM resources. See below example on using `Where` and `WhereNot`
109+
```go
110+
func (r *YourFunction) Run(context *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects) {
111+
namespaceScopedObjects := objects.Where(func(o *fn.KubeObject) bool { return o.IsNamespaceScoped() })
112+
clusterScopedObjects := objects.Where(func(o *fn.KubeObject) bool { return o.IsClusterScoped() })
113+
targetKindObjects := objects.Where(fn.IsGVK("", "", "CustomDeployment") })
114+
excludeObjects := objects.WhereNot(fn.IsGVK("v1", "", "Namespace") })
115+
}
116+
```
117+
118+
#### Read/Write a field path of a KRM resource
119+
120+
Like [unstructured.Unstructured], `fn.KubeObject` (and `fn.SubObject`) provides a series of read and write
121+
methods to let you read and write different types of resources. You can work on
122+
123+
```go
124+
func (r *YourFunction) Run(context *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects) {
125+
deployment := items.Where(fn.IsGVK("apps", "v1", "Deployment")).Where(func(o *fn.KubeObject) bool{return o.GetName() == "nginx"})[0]
126+
replicas := deployment.NestedInt64OrDie("spec", "replicas")
127+
fn.Logf("replicas is %v\n", replicas)
128+
paused := obj.NestedBoolOrDie("spec", "paused")
129+
fn.Logf("paused is %v\n", paused)
130+
// Update strategy from Recreate to RollingUpdate.
131+
if strategy := obj.NestedStringOrDie("spec", "strategy", "type"); strategy == "Recreate" {
132+
obj.SetNestedStringOrDie("RollingUpdate", "spec", "strategy", "type")
133+
}
134+
}
135+
```
136+
137+
Besides the [unstructured.Unstructured] style, you can also run functions on each sub-field as a `fn.SubObject`
138+
```go
139+
func (r *YourFunction) Run(context *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects) {
140+
// operate each resource layer via `GetMap`
141+
deployment := items.Where(fn.IsGVK("apps", "v1", "Deployment")).Where(func(o *fn.KubeObject) bool{return o.GetName() == "nginx"})[0]
142+
spec := deployment.GetMap("spec")
143+
replicas = spec.GetInt("replicas")
144+
fn.Logf("replicas is %v\n", replicas)
145+
nodeSelector := spec.GetMap("template").GetMap("spec").GetMap("nodeSelector")
146+
if nodeSelector.GetString("disktype") != "ssd" {
147+
nodeSelector.SetNestedStringOrDie("ssd", "disktype")
148+
}
149+
```
150+
151+
#### Copy `KubeObject` to a typed struct
152+
153+
If you already have some struct to define a KRM resource (like `corev1.ConfigMap`), you can switch the `KubeObject`
154+
to the other type via `As`
155+
```go
156+
func (r *YourFunction) Run(context *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects) {
157+
deploymentObject := objects.WhereNot(fn.IsGVK("apps", "v1", "Deployment") })[0]
158+
deploymentSpec := deploymentObject.GetMap("spec")
159+
var dpSpec appsv1.DeploymentSpec
160+
deploymentSpec.As(&dpSpec)
161+
dpSpec.Size()
162+
}
163+
```
164+
165+
<!-- TODO: We need a "Test the KRM function" section, which reuqires the SDK to provide the test infra so users only provide the input resource and expected output in YAML--->
166+
167+
[Install kpt]:
168+
https://kpt.dev/installation/
169+
[Install Docker]:
170+
https://docs.docker.com/get-docker/
171+
[`fn.ResourceList`]:
172+
https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn#ResourceList
173+
[`fn.KubeObject` and `fn.SubObject`]:
174+
https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn#KubeObject
175+
[Golang]:
176+
https://go.dev/doc/gopath_code
177+
[Runner]:
178+
https://pkg.go.dev/github.com/GoogleContainerTools/kpt-functions-sdk/go/fn#Runner
179+
[unstructured.Unstructured]:
180+
https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
181+
["Developing in Go"]:
182+
https://kpt.dev/book/05-developing-functions/02-developing-in-Go

0 commit comments

Comments
 (0)