Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions go/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
run:
deadline: 5m

linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
# - funlen
- gochecknoinits
- goconst
# - gocritic
# - gocyclo
- gofmt
- goimports
- golint
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- scopelint
- staticcheck
- structcheck
# stylecheck demands that acronyms not be treated as words
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
# - stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

linters-settings:
dupl:
threshold: 400
lll:
line-length: 170
gocyclo:
min-complexity: 15
golint:
min-confidence: 0.85
2 changes: 1 addition & 1 deletion go/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fmt:

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

test:
go test -cover ./...
Expand Down
50 changes: 50 additions & 0 deletions go/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

/*
Package krmfn.provides an SDK for writing KRM functions in Go. The function
specification is defined at:
https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

Note: this package is an krmfn.package.

A KRM functions can generate, mutate or validate Kubernetes resources in a
ResourceList.

The ResourceList type and the KubeObject type are the core part of this package.
The ResourceList type maps to the ResourceList in the function spec. The
KubeObject represent a kubernetes resource in a ResourceList, and it's the basic
unit to perform most CRUD operations.

A KRM function does the following things:

1. read yaml bytes from stdin and convert it to a ResourceList
2. perform mutation and validation on the resources in the ResourceList
3. write the updated ResourceList out to stdout in yaml format
4. Any diagnostic messages should be written to stderr

ResourceListProcessor

In most cases, you only need to do #2 which is implementing a
ResourceListProcessor and then pass it to AsMain. In the following example, we
use ResourceListProcessorFunc that implements the ResourceListProcessor
interface.

func main() {
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(myfn)); err != nil {
os.Exit(1)
}
}

func myfn(rl *krmfn.ResourceList) error {
krmfn.Log("log something")
// mutate or validate the ResourceList
}

KubeObject

KubeObject hides all the details about yaml.Node and yaml.RNode. It is always
recommended converting a KubeObject to a strong typed object or getting a field
as a strong typed object. Then do the CRUD operation on the strong typed objects.
*/
package krmfn
57 changes: 57 additions & 0 deletions go/document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package krmfn

import (
"bytes"
"io"

"sigs.k8s.io/kustomize/kyaml/yaml"
)

type doc struct {
nodes []*yaml.Node
}

func newDoc(nodes ...*yaml.Node) *doc {
return &doc{nodes: nodes}
}

func parseDoc(b []byte) (*doc, error) {
br := bytes.NewReader(b)

var nodes []*yaml.Node
decoder := yaml.NewDecoder(br)
for {
node := &yaml.Node{}
if err := decoder.Decode(node); err != nil {
if err == io.EOF {
break
}
return nil, err
}
nodes = append(nodes, node)
}

return &doc{nodes: nodes}, nil
}

func (d *doc) ToYAML() ([]byte, error) {
var w bytes.Buffer
encoder := yaml.NewEncoder(&w)
for _, node := range d.nodes {
if node.Kind == yaml.DocumentNode {
if len(node.Content) == 0 {
// These cause errors when we try to write them
continue
}
}
if err := encoder.Encode(node); err != nil {
return nil, err
}
}

return w.Bytes(), nil
}

func (d *doc) Objects() ([]*mapVariant, error) {
return extractObjects(d.nodes...)
}
30 changes: 30 additions & 0 deletions go/examples/example_filter_GVK_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package example_test

import (
"os"

"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
)

// This example implements a function that updates the replicas field for all deployments.

func Example_filterGVK() {
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(updateReplicas)); err != nil {
os.Exit(1)
}
}

// updateReplicas sets a field in resources selecting by GVK.
func updateReplicas(rl *krmfn.ResourceList) error {
if rl.FunctionConfig == nil {
return krmfn.ErrMissingFnConfig{}
}
var replicas int
rl.FunctionConfig.GetOrDie(&replicas, "replicas")
for i, obj := range rl.Items {
if obj.APIVersion() == "apps/v1" && obj.Kind() == "Deployment" {
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
}
}
return nil
}
68 changes: 68 additions & 0 deletions go/examples/example_generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package example

import (
"fmt"
"io/ioutil"
"net/http"
"os"

"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"

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 Example_generator() {
if err := krmfn.AsMain(krmfn.ResourceListProcessorFunc(generate)); err != nil {
os.Exit(1)
}
}

// generate generates a ConfigMap.
func generate(rl *krmfn.ResourceList) error {
if rl.FunctionConfig == nil {
return krmfn.ErrMissingFnConfig{}
}

revision := rl.FunctionConfig.GetStringOrDie("data", "revision")
id := rl.FunctionConfig.GetStringOrDie("data", "id")
js, err := fetchDashboard(revision, id)
if err != nil {
return 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.Name()),
Namespace: rl.FunctionConfig.Namespace(),
Labels: map[string]string{
"grafana_dashboard": "true",
},
},
Data: map[string]string{
fmt.Sprintf("%v.json", rl.FunctionConfig.Name()): fmt.Sprintf("%q", js),
},
}
return 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)
if err != nil {
return "", err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(b), nil
}
59 changes: 59 additions & 0 deletions go/examples/example_logger_injector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package example

import (
"os"

corev1 "k8s.io/api/core/v1"
yaml2 "sigs.k8s.io/kustomize/kyaml/yaml"

"github.com/GoogleContainerTools/kpt-functions-sdk/krmfn"
)

// In this example, we implement a function that injects a logger as a sidecar
// container in workload APIs.

func Example_loggeInjector() {
if err := krmfn.AsMain(krmfn.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 *krmfn.ResourceList) error {
var li LoggerInjection
if err := rl.FunctionConfig.As(&li); err != nil {
return err
}
for i, obj := range rl.Items {
if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") {
var containers []corev1.Container
obj.GetOrDie(&containers, "spec", "template", "spec", "containers")
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)
}
rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers")
}
}
return 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"`
}
59 changes: 59 additions & 0 deletions go/examples/example_logger_injector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package example

import (
"os"

corev1 "k8s.io/api/core/v1"
yaml2 "github.com/GoogleContainerTools/kpt-functions-sdk/internal/forked/kyaml/yaml"

"github.com/GoogleContainerTools/kpt-functions-sdk/alpha"
)

// In this example, we implement a function that injects a logger as a sidecar
// container in workload APIs.

func Example_loggeInjector() {
if err := alpha.AsMain(alpha.ResourceListProcessorFunc(injectLogger)); err != nil {
os.Exit(1)
}
}

// injectLogger injects a logger container into the workload API resources.
// generate implements the goalpha.KRMFunction interface.
func injectLogger(rl *alpha.ResourceList) error {
var li LoggerInjection
if err := rl.FunctionConfig.As(&li); err != nil {
return err
}
for i, obj := range rl.Items {
if obj.APIVersion() == "apps/v1" && (obj.Kind() == "Deployment" || obj.Kind() == "StatefulSet" || obj.Kind() == "DaemonSet" || obj.Kind() == "ReplicaSet") {
var containers []corev1.Container
obj.GetOrDie(&containers, "spec", "template", "spec", "containers")
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)
}
rl.Items[i].SetOrDie(containers, "spec", "template", "spec", "containers")
}
}
return 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"`
}
Loading