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