Documentation
¶
Overview ¶
Example (AReadField) ¶
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// In this example, we read a field from the input object and print it to the log.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(readField)); err != nil {
os.Exit(1)
}
}
func readField(rl *fn.ResourceList) (bool, error) {
for _, obj := range rl.Items.Where(fn.IsGVK("apps", "v1", "Deployment")) {
// Style 1: like using unstrucuted.Unstructured, get/set the value from field paths*
replicas, _, _ := obj.NestedInt64("spec", "replicas")
fn.Logf("replicas is %v\n", replicas)
paused, _, _ := obj.NestedBool("spec", "paused")
fn.Logf("paused is %v\n", paused)
// Update strategy from Recreate to RollingUpdate.
if strategy, _, _ := obj.NestedString("spec", "strategy", "type"); strategy == "Recreate" {
if err := obj.SetNestedString("RollingUpdate", "spec", "strategy", "type"); err != nil {
return false, err
}
}
// Style 2: operate each resource layer via `GetMap`
spec := obj.GetMap("spec")
replicas = spec.GetInt("replicas")
fn.Logf("replicas is %v\n", replicas)
nodeSelector := spec.GetMap("template").GetMap("spec").GetMap("nodeSelector")
if nodeSelector.GetString("disktype") != "ssd" {
if err := nodeSelector.SetNestedString("ssd", "disktype"); err != nil {
return false, err
}
}
}
return true, nil
}
Example (AsMain) ¶
This example uses a SetLabels object, which implements `Runner.Run` methods.
The input from ./data/setlabels-resourcelist.yaml: apiVersion: config.kubernetes.io/v1 kind: ResourceList items:
- apiVersion: v1 kind: Service metadata: name: example
functionConfig:
apiVersion: fn.kpt.dev/v1alpha1 kind: SetLabels metadata: name: setlabel-fn-config
package main
import (
"context"
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
var _ fn.Runner = &SetLabels{}
type SetLabels struct {
Labels map[string]string `json:"labels,omitempty"`
}
// Run is the main function logic.
// `ctx` provides easy methods to add info, error or warning result to `ResourceList.Results`.
// `items` is parsed from the STDIN "ResourceList.Items".
// `functionConfig` is from the STDIN "ResourceList.FunctionConfig". The value has been assigned to the r.Labels
//
// the functionConfig is validated to have kind "SetLabels" and apiVersion "fn.kpt.dev/v1alpha1"
func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool {
for _, o := range items {
for k, newLabel := range r.Labels {
err := o.SetLabel(k, newLabel)
if err != nil {
results.ErrorE(err)
// continue even if error occurs, we want the final results to show all errors.
}
}
}
if results.ExitCode() != 1 {
results.Infof("updated labels")
}
return true
}
// This example uses a SetLabels object, which implements `Runner.Run` methods.
//
// The input from ./data/setlabels-resourcelist.yaml:
// apiVersion: config.kubernetes.io/v1
// kind: ResourceList
// items:
// - apiVersion: v1
// kind: Service
// metadata:
// name: example
//
// functionConfig:
//
// apiVersion: fn.kpt.dev/v1alpha1
// kind: SetLabels
// metadata:
// name: setlabel-fn-config
func main() {
file, _ := os.Open("./data/setlabels-resourcelist.yaml")
defer file.Close()
os.Stdin = file
ctx := context.TODO()
if err := fn.AsMain(fn.WithContext(ctx, &SetLabels{})); err != nil {
os.Exit(1)
}
}
Output: apiVersion: config.kubernetes.io/v1 kind: ResourceList items: - apiVersion: v1 kind: Service metadata: name: example functionConfig: apiVersion: fn.kpt.dev/v1alpha1 kind: SetLabels metadata: name: setlabel-fn-config results: - message: updated labels severity: info
Example (BReadFunctionConfig) ¶
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
yaml2 "sigs.k8s.io/kustomize/kyaml/yaml"
)
// In this example, we convert the functionConfig as strong typed object and then
// read a field from the functionConfig object.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(readFunctionConfig)); err != nil {
os.Exit(1)
}
}
func readFunctionConfig(rl *fn.ResourceList) (bool, error) {
var sr SetReplicas
if err := rl.FunctionConfig.As(&sr); err != nil {
return false, err
}
fn.Logf("desired replicas is %v\n", sr.DesiredReplicas)
return true, nil
}
// SetReplicas is the type definition of the functionConfig
type SetReplicas struct {
yaml2.ResourceIdentifier `json:",inline" yaml:",inline"`
DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"`
}
Example (BuiltinFunction) ¶
package main
import (
"bytes"
"fmt"
"strings"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// This example removes all resources as a builtin function by using fn.Execute.
type RemoveAllResources struct{}
func (*RemoveAllResources) Process(rl *fn.ResourceList) (bool, error) {
rl.Items = nil
return true, nil
}
func main() {
reader := strings.NewReader(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
items:
- kind: Deployment
metadata:
name: my-deploy
- kind: Service
metadata:
name: my-service
functionConfig:
apiVersion: fn.kpt.dev/v1alpha1
kind: RemoveAllResources
metadata:
name: fn-config`)
var writer bytes.Buffer
err := fn.Execute(&RemoveAllResources{}, reader, &writer)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(writer.String())
}
Output: apiVersion: config.kubernetes.io/v1 kind: ResourceList items: [] functionConfig: apiVersion: fn.kpt.dev/v1alpha1 kind: RemoveAllResources metadata: name: fn-config
Example (CSetField) ¶
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// In this example, we read a field from the input object and print it to the log.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(setField)); err != nil {
os.Exit(1)
}
}
func setField(rl *fn.ResourceList) (bool, error) {
for _, obj := range rl.Items {
if obj.GetAPIVersion() == "apps/v1" && obj.GetKind() == "Deployment" {
replicas := 10
if err := obj.SetNestedField(&replicas, "spec", "replicas"); err != nil {
return false, err
}
}
}
return true, nil
}
Example (DMutateComments) ¶
package main
import (
"os"
"strings"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// In this example, we mutate line comments for field metadata.name.
// Some function may want to store some information in the comments (e.g.
// apply-setters function: https://catalog.kpt.dev/apply-setters/v0.2/)
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(mutateComments)); err != nil {
os.Exit(1)
}
}
func mutateComments(rl *fn.ResourceList) (bool, error) {
for i := range rl.Items {
lineComment, found, err := rl.Items[i].LineComment("metadata", "name")
if err != nil {
return false, err
}
if !found {
return true, nil
}
if strings.TrimSpace(lineComment) == "" {
lineComment = "bar-system"
} else {
lineComment = strings.Replace(lineComment, "foo", "bar", -1)
}
if err = rl.Items[i].SetLineComment(lineComment, "metadata", "name"); err != nil {
return false, err
}
}
return true, nil
}
Example (FilterGVK) ¶
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// This example implements a function that updates the replicas field for all deployments.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(updateReplicas)); err != nil {
os.Exit(1)
}
}
// updateReplicas sets a field in resources selecting by GVK.
func updateReplicas(rl *fn.ResourceList) (bool, error) {
if rl.FunctionConfig == nil {
return false, fn.ErrMissingFnConfig{}
}
var replicas int
found, err := rl.FunctionConfig.NestedResource(&replicas, "replicas")
if err != nil || !found {
return found, err
}
for i := range rl.Items.Where(fn.IsGVK("apps", "v1", "Deployment")) {
if err := rl.Items[i].SetNestedField(replicas, "spec", "replicas"); err != nil {
return false, err
}
}
return true, nil
}
Example (Generator) ¶
package main
import (
"fmt"
"io"
"net/http"
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// This function generates Graphana configuration in the form of ConfigMap. It
// accepts Revision and ID as input.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(generate)); err != nil {
os.Exit(1)
}
}
// generate generates a ConfigMap.
func generate(rl *fn.ResourceList) (bool, error) {
if rl.FunctionConfig == nil {
return false, fn.ErrMissingFnConfig{}
}
revision, _, _ := rl.FunctionConfig.NestedString("data", "revision")
id, _, _ := rl.FunctionConfig.NestedString("data", "id")
js, err := fetchDashboard(revision, id)
if err != nil {
return false, fmt.Errorf("fetch dashboard: %v", err)
}
cm := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%v-gen", rl.FunctionConfig.GetName()),
Namespace: rl.FunctionConfig.GetNamespace(),
Labels: map[string]string{
"grafana_dashboard": "true",
},
},
Data: map[string]string{
fmt.Sprintf("%v.json", rl.FunctionConfig.GetName()): fmt.Sprintf("%q", js),
},
}
return true, rl.UpsertObjectToItems(cm, nil, false)
}
func fetchDashboard(revision, id string) (string, error) {
url := fmt.Sprintf("https://grafana.com/api/dashboards/%s/revisions/%s/download", id, revision)
resp, err := http.Get(url) // nolint:gosec
if err != nil {
return "", err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(b), nil
}
Example (KubeObjectMutatePrimitiveField) ¶
package main
import (
"github.com/kptdev/krm-functions-sdk/go/fn"
)
var deployment fn.KubeObject
func main() {
spec := deployment.GetMap("spec")
replicas := spec.GetInt("replicas")
// mutate the replicas variable
err := spec.SetNestedInt(int(replicas))
if err != nil {
panic(err)
}
}
Example (KubeObjectMutatePrimitiveMap) ¶
package main
import (
"github.com/kptdev/krm-functions-sdk/go/fn"
)
var (
deployment fn.KubeObject
configMap fn.KubeObject
)
func main() {
data, _, _ := configMap.NestedStringMap("data")
// mutate the data map
err := deployment.SetNestedStringMap(data, "data")
if err != nil { /* do something */
}
}
Example (KubeObjectMutatePrimitiveSlice) ¶
package main
import (
"github.com/kptdev/krm-functions-sdk/go/fn"
)
var deployment fn.KubeObject
func main() {
finalizers, _, _ := deployment.NestedStringSlice("metadata", "finalizers")
// mutate the finalizers slice
err := deployment.SetNestedStringSlice(finalizers, "metadata", "finalizers")
if err != nil {
panic(err)
}
}
Example (KubeObjectMutateStrongTypedField) ¶
package main
import (
"github.com/kptdev/krm-functions-sdk/go/fn"
corev1 "k8s.io/api/core/v1"
)
var (
deployment fn.KubeObject
configMap fn.KubeObject
)
func main() {
var newPodTemplate corev1.PodTemplate
curPodTemplate := configMap.GetMap("spec").GetMap("template")
// Assign the current PodTemplate value to newPodTemplate
// Use As to AsMain handles the errors.
err := curPodTemplate.As(&newPodTemplate)
if err != nil {
panic(err)
}
// mutate the newPodTemplate object
err = deployment.SetNestedField(newPodTemplate, "spec", "template")
if err != nil { /* do something */
panic(err)
}
}
Example (KubeObjectMutateStrongTypedSlice) ¶
package main
import (
"github.com/kptdev/krm-functions-sdk/go/fn"
corev1 "k8s.io/api/core/v1"
)
var deployment fn.KubeObject
func main() {
var containers []corev1.Container
found, err := deployment.NestedResource(&containers, "spec", "template", "spec", "containers")
if err != nil { /* do something */
}
if !found { /* do something */
}
// mutate the podTemplate object
err = deployment.SetNestedField(containers, "spec", "template", "spec", "containers")
if err != nil { /* do something */
}
}
Example (LoggeInjector) ¶
package main
import (
"os"
corev1 "k8s.io/api/core/v1"
yaml2 "sigs.k8s.io/kustomize/kyaml/yaml"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// In this example, we implement a function that injects a logger as a sidecar
// container in workload APIs.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(injectLogger)); err != nil {
os.Exit(1)
}
}
// injectLogger injects a logger container into the workload API resources.
// generate implements the gokrmfn.KRMFunction interface.
func injectLogger(rl *fn.ResourceList) (bool, error) {
var li LoggerInjection
if err := rl.FunctionConfig.As(&li); err != nil {
return false, err
}
for i, obj := range rl.Items.Where(hasDesiredGVK) {
var containers []corev1.Container
found, err := obj.NestedResource(&containers, "spec", "template", "spec", "containers")
if err != nil || !found {
return found, err
}
foundTargetContainer := false
for j, container := range containers {
if container.Name == li.ContainerName {
containers[j].Image = li.ImageName
foundTargetContainer = true
break
}
}
if !foundTargetContainer {
c := corev1.Container{
Name: li.ContainerName,
Image: li.ImageName,
}
containers = append(containers, c)
}
if err = rl.Items[i].SetNestedField(containers, "spec", "template", "spec", "containers"); err != nil {
return false, nil
}
}
return true, nil
}
// LoggerInjection is type definition of the functionConfig.
type LoggerInjection struct {
yaml2.ResourceMeta `json:",inline" yaml:",inline"`
ContainerName string `json:"containerName" yaml:"containerName"`
ImageName string `json:"imageName" yaml:"imageName"`
}
Example (SelectExclude) ¶
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// This example implements a function that selectively includes or excludes some resources.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(selectResources)); err != nil {
os.Exit(1)
}
}
// selectResources keeps all resources with the GVK apps/v1 Deployment that do
// NOT have the label foo=bar, and removes the rest.
func selectResources(rl *fn.ResourceList) (bool, error) {
rl.Items = rl.Items.Where(fn.IsGVK("apps", "v1", "Deployment")).
WhereNot(fn.HasLabels(map[string]string{"foo": "bar"}))
return true, nil
}
Example (Validator) ¶
This example implements a function that validate resources to ensure spec.template.spec.securityContext.runAsNonRoot is set in workload APIs.
package main
import (
"os"
"github.com/kptdev/krm-functions-sdk/go/fn"
)
// This example implements a function that validate resources to ensure
// spec.template.spec.securityContext.runAsNonRoot is set in workload APIs.
func main() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(validator)); err != nil {
os.Exit(1)
}
}
func validator(rl *fn.ResourceList) (bool, error) {
var results fn.Results
for _, obj := range rl.Items.Where(hasDesiredGVK) {
var runAsNonRoot bool
if _, err := obj.NestedResource(&runAsNonRoot, "spec", "template", "spec", "securityContext", "runAsNonRoot"); err != nil {
return false, err
}
if !runAsNonRoot {
results = append(results, fn.ConfigObjectResult("`spec.template.spec.securityContext.runAsNonRoot` must be set to true", obj, fn.Error))
}
}
return true, results
}
Click to show internal directories.
Click to hide internal directories.