Skip to content

Commit 4c54e1c

Browse files
authored
Init alpha dir, fork kyaml (#517)
1 parent 6964a09 commit 4c54e1c

31 files changed

+4035
-4
lines changed

go/.golangci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
run:
2+
deadline: 5m
3+
4+
linters:
5+
disable-all: true
6+
enable:
7+
- bodyclose
8+
- deadcode
9+
- depguard
10+
- dogsled
11+
- dupl
12+
- errcheck
13+
# - funlen
14+
- gochecknoinits
15+
- goconst
16+
# - gocritic
17+
# - gocyclo
18+
- gofmt
19+
- goimports
20+
- golint
21+
- gosec
22+
- gosimple
23+
- govet
24+
- ineffassign
25+
- interfacer
26+
- lll
27+
- misspell
28+
- nakedret
29+
- scopelint
30+
- staticcheck
31+
- structcheck
32+
# stylecheck demands that acronyms not be treated as words
33+
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
34+
# - stylecheck
35+
- typecheck
36+
- unconvert
37+
- unparam
38+
- unused
39+
- varcheck
40+
- whitespace
41+
42+
linters-settings:
43+
dupl:
44+
threshold: 400
45+
lll:
46+
line-length: 170
47+
gocyclo:
48+
min-complexity: 15
49+
golint:
50+
min-confidence: 0.85

go/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fmt:
1717

1818
lint:
1919
(which $(GOPATH)/bin/golangci-lint || go get github.com/golangci/golangci-lint/cmd/golangci-lint)
20-
$(GOPATH)/bin/golangci-lint run ./...
20+
$(GOPATH)/bin/golangci-lint -c ./.golangci.yml run ./...
2121

2222
test:
2323
go test -cover ./...

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 written 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+
}

0 commit comments

Comments
 (0)