Skip to content

Commit 2c9e242

Browse files
committed
init sdk (KubeObject, variant in kpt-funtions-catalog)
1 parent 03b0743 commit 2c9e242

29 files changed

+3991
-3
lines changed

go/doc.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2022 The Kubernetes Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*
5+
Package krmfn.provides an SDK for writing KRM functions in Go. The function
6+
specification is defined at:
7+
https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md
8+
9+
Note: this package is an krmfn.package.
10+
11+
A KRM functions can generate, mutate or validate Kubernetes resources in a
12+
ResourceList.
13+
14+
The ResourceList type and the KubeObject type are the core part of this package.
15+
The ResourceList type maps to the ResourceList in the function spec. The
16+
KubeObject represent a kubernetes resource in a ResourceList, and it's the basic
17+
unit to perform most CRUD operations.
18+
19+
A KRM function does the following things:
20+
21+
1. read yaml bytes from stdin and convert it to a ResourceList
22+
2. perform mutation and validation on the resources in the ResourceList
23+
3. write the updated ResourceList out to stdout in yaml format
24+
4. Any diagnostic messages should be writen to stderr
25+
26+
ResourceListProcessor
27+
28+
In most cases, you only need to do #2 which is implementing a
29+
ResourceListProcessor and then pass it to AsMain. In the following example, we
30+
use ResourceListProcessorFunc that implements the ResourceListProcessor
31+
interface.
32+
33+
func main() {
34+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(myfn)); err != nil {
35+
os.Exit(1)
36+
}
37+
}
38+
39+
func myfn(rl *krmfn.ResourceList) error {
40+
krmfn.Log("log something")
41+
// mutate or validate the ResourceList
42+
}
43+
44+
KubeObject
45+
46+
KubeObject hides all the details about yaml.Node and yaml.RNode. It is always
47+
recommended converting a KubeObject to a strong typed object or getting a field
48+
as a strong typed object. Then do the CRUD operation on the strong typed objects.
49+
*/
50+
package krmfn

go/document.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package krmfn
2+
3+
import (
4+
"bytes"
5+
"io"
6+
7+
"sigs.k8s.io/kustomize/kyaml/yaml"
8+
)
9+
10+
type doc struct {
11+
nodes []*yaml.Node
12+
}
13+
14+
func newDoc(nodes ...*yaml.Node) *doc {
15+
return &doc{nodes: nodes}
16+
}
17+
18+
func parseDoc(b []byte) (*doc, error) {
19+
br := bytes.NewReader(b)
20+
21+
var nodes []*yaml.Node
22+
decoder := yaml.NewDecoder(br)
23+
for {
24+
node := &yaml.Node{}
25+
if err := decoder.Decode(node); err != nil {
26+
if err == io.EOF {
27+
break
28+
}
29+
return nil, err
30+
}
31+
nodes = append(nodes, node)
32+
}
33+
34+
return &doc{nodes: nodes}, nil
35+
}
36+
37+
func (d *doc) ToYAML() ([]byte, error) {
38+
var w bytes.Buffer
39+
encoder := yaml.NewEncoder(&w)
40+
for _, node := range d.nodes {
41+
if node.Kind == yaml.DocumentNode {
42+
if len(node.Content) == 0 {
43+
// These cause errors when we try to write them
44+
continue
45+
}
46+
}
47+
if err := encoder.Encode(node); err != nil {
48+
return nil, err
49+
}
50+
}
51+
52+
return w.Bytes(), nil
53+
}
54+
55+
func (d *doc) Objects() ([]*mapVariant, error) {
56+
return extractObjects(d.nodes...)
57+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package example_test
2+
3+
import (
4+
"os"
5+
6+
"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
7+
)
8+
9+
// This example implements a function that updates the replicas field for all deployments.
10+
11+
func Example_filterGVK() {
12+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(updateReplicas)); err != nil {
13+
os.Exit(1)
14+
}
15+
}
16+
17+
// updateReplicas sets a field in resources selecting by GVK.
18+
func updateReplicas(rl *krmfn.ResourceList) error {
19+
if rl.FunctionConfig == nil {
20+
return krmfn.ErrMissingFnConfig{}
21+
}
22+
var replicas int
23+
rl.FunctionConfig.GetOrDie(&replicas, "replicas")
24+
for i, obj := range rl.Items {
25+
if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" {
26+
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
27+
}
28+
}
29+
return nil
30+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package example
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"net/http"
7+
"os"
8+
9+
"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
10+
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
)
14+
15+
// This function generates Graphana configuration in the form of ConfigMap. It
16+
// accepts Revision and ID as input.
17+
18+
func Example_generator() {
19+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(generate)); err != nil {
20+
os.Exit(1)
21+
}
22+
}
23+
24+
// generate generates a ConfigMap.
25+
func generate(rl *krmfn.ResourceList) error {
26+
if rl.FunctionConfig == nil {
27+
return krmfn.ErrMissingFnConfig{}
28+
}
29+
30+
revision := rl.FunctionConfig.GetStringOrDie("data", "revision")
31+
id := rl.FunctionConfig.GetStringOrDie("data", "id")
32+
js, err := fetchDashboard(revision, id)
33+
if err != nil {
34+
return fmt.Errorf("fetch dashboard: %v", err)
35+
}
36+
37+
cm := corev1.ConfigMap{
38+
TypeMeta: metav1.TypeMeta{
39+
APIVersion: "v1",
40+
Kind: "ConfigMap",
41+
},
42+
ObjectMeta: metav1.ObjectMeta{
43+
Name: fmt.Sprintf("%v-gen", rl.FunctionConfig.Name()),
44+
Namespace: rl.FunctionConfig.Namespace(),
45+
Labels: map[string]string{
46+
"grafana_dashboard": "true",
47+
},
48+
},
49+
Data: map[string]string{
50+
fmt.Sprintf("%v.json", rl.FunctionConfig.Name()): fmt.Sprintf("%q", js),
51+
},
52+
}
53+
return rl.UpsertObjectToItems(cm, nil, false)
54+
}
55+
56+
func fetchDashboard(revision, id string) (string, error) {
57+
url := fmt.Sprintf("https://grafana.com/api/dashboards/%s/revisions/%s/download", id, revision)
58+
resp, err := http.Get(url)
59+
if err != nil {
60+
return "", err
61+
}
62+
defer resp.Body.Close()
63+
b, err := ioutil.ReadAll(resp.Body)
64+
if err != nil {
65+
return "", err
66+
}
67+
return string(b), nil
68+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package example
2+
3+
import (
4+
"os"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
yaml2 "sigs.k8s.io/kustomize/kyaml/yaml"
8+
9+
"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
10+
)
11+
12+
// In this example, we implement a function that injects a logger as a sidecar
13+
// container in workload APIs.
14+
15+
func Example_loggeInjector() {
16+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(injectLogger)); err != nil {
17+
os.Exit(1)
18+
}
19+
}
20+
21+
// injectLogger injects a logger container into the workload API resources.
22+
// generate implements the gokrmfn.KRMFunction interface.
23+
func injectLogger(rl *krmfn.ResourceList) error {
24+
var li LoggerInjection
25+
if err := rl.FunctionConfig.As(&li); err != nil {
26+
return err
27+
}
28+
for i, obj := range rl.Items {
29+
if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") {
30+
var containers []corev1.Container
31+
obj.GetOrDie(&containers, "spec", "template", "spec", "containers")
32+
foundTargetContainer := false
33+
for j, container := range containers {
34+
if container.Name == li.ContainerName {
35+
containers[j].Image = li.ImageName
36+
foundTargetContainer = true
37+
break
38+
}
39+
}
40+
if !foundTargetContainer {
41+
c := corev1.Container{
42+
Name: li.ContainerName,
43+
Image: li.ImageName,
44+
}
45+
containers = append(containers, c)
46+
}
47+
rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers")
48+
}
49+
}
50+
return nil
51+
}
52+
53+
// LoggerInjection is type definition of the functionConfig.
54+
type LoggerInjection struct {
55+
yaml2.ResourceMeta `json:",inline" yaml:",inline"`
56+
57+
ContainerName string `json:"containerName" yaml:"containerName"`
58+
ImageName string `json:"imageName" yaml:"imageName"`
59+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package example
2+
3+
import (
4+
"os"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
yaml2 "github.com/GoogleContainerTools/kpt-functions-sdk/internal/forked/kyaml/yaml"
8+
9+
"github.com/GoogleContainerTools/kpt-functions-sdk/alpha"
10+
)
11+
12+
// In this example, we implement a function that injects a logger as a sidecar
13+
// container in workload APIs.
14+
15+
func Example_loggeInjector() {
16+
if err := alpha.AsMain(alpha.ResourceListProcessorFunc(injectLogger)); err != nil {
17+
os.Exit(1)
18+
}
19+
}
20+
21+
// injectLogger injects a logger container into the workload API resources.
22+
// generate implements the goalpha.KRMFunction interface.
23+
func injectLogger(rl *alpha.ResourceList) error {
24+
var li LoggerInjection
25+
if err := rl.FunctionConfig.As(&li); err != nil {
26+
return err
27+
}
28+
for i, obj := range rl.Items {
29+
if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") {
30+
var containers []corev1.Container
31+
obj.GetOrDie(&containers, "spec", "template", "spec", "containers")
32+
foundTargetContainer := false
33+
for j, container := range containers {
34+
if container.Name == li.ContainerName {
35+
containers[j].Image = li.ImageName
36+
foundTargetContainer = true
37+
break
38+
}
39+
}
40+
if !foundTargetContainer {
41+
c := corev1.Container{
42+
Name: li.ContainerName,
43+
Image: li.ImageName,
44+
}
45+
containers = append(containers, c)
46+
}
47+
rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers")
48+
}
49+
}
50+
return nil
51+
}
52+
53+
// LoggerInjection is type definition of the functionConfig.
54+
type LoggerInjection struct {
55+
yaml2.ResourceMeta `json:",inline" yaml:",inline"`
56+
57+
ContainerName string `json:"containerName" yaml:"containerName"`
58+
ImageName string `json:"imageName" yaml:"imageName"`
59+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package example
2+
3+
import (
4+
"os"
5+
"strings"
6+
7+
"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
8+
)
9+
10+
// In this example, we mutate line comments for field metadata.name.
11+
// Some function may want to store some information in the comments (e.g.
12+
// apply-setters function: https://catalog.kpt.dev/apply-setters/v0.2/)
13+
14+
func Example_dMutateComments() {
15+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(mutateComments)); err != nil {
16+
os.Exit(1)
17+
}
18+
}
19+
20+
func mutateComments(rl *krmfn.ResourceList) error {
21+
for i := range rl.Items {
22+
lineComment, found, err := rl.Items[i].LineComment("metadata", "name")
23+
if err != nil {
24+
return err
25+
}
26+
if !found {
27+
return nil
28+
}
29+
30+
if strings.TrimSpace(lineComment) == "" {
31+
lineComment = "bar-system"
32+
} else {
33+
lineComment = strings.Replace(lineComment, "foo", "bar", -1)
34+
}
35+
if err = rl.Items[i].SetLineComment(lineComment, "metadata", "name"); err != nil {
36+
return err
37+
}
38+
}
39+
return nil
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package example
2+
3+
import (
4+
"os"
5+
6+
"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
7+
)
8+
9+
// In this example, we read a field from the input object and print it to the log.
10+
11+
func Example_aReadField() {
12+
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(readField)); err != nil {
13+
os.Exit(1)
14+
}
15+
}
16+
17+
func readField(rl *krmfn.ResourceList) error {
18+
for _, obj := range rl.Items {
19+
if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" {
20+
var replicas int
21+
obj.GetOrDie(&replicas, "spec", "replicas")
22+
krmfn.Logf("replicas is %v\n", replicas)
23+
}
24+
}
25+
return nil
26+
}

0 commit comments

Comments
 (0)