Skip to content

Commit

Permalink
Basic config map generator function (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnbelamaric authored Sep 14, 2023
1 parent facd956 commit cf3a496
Show file tree
Hide file tree
Showing 25 changed files with 880 additions and 0 deletions.
1 change: 1 addition & 0 deletions krm-functions/gen-configmap-fn/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_actual_output.yaml
25 changes: 25 additions & 0 deletions krm-functions/gen-configmap-fn/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2023 The Nephio Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.20-alpine
ENV CGO_ENABLED=0
WORKDIR /go/src/
COPY krm-functions/ krm-functions/
WORKDIR /go/src/krm-functions/gen-configmap-fn
RUN go install
RUN go build -o /usr/local/bin/function ./

FROM gcr.io/distroless/static:latest
COPY --from=0 /usr/local/bin/function /usr/local/bin/function
ENTRYPOINT ["function"]
20 changes: 20 additions & 0 deletions krm-functions/gen-configmap-fn/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
IMAGE_TAG ?= latest
REGISTRY ?= docker.io/nephio
IMAGE_NAME ?= gen-configmap-fn
IMG ?= $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)

# This includes the following targets:
# test, unit, unit-clean,
# gosec, lint,
# fmt, vet
include ../../default-go.mk

# This includes the following targets:
# docker-build, docker-push
include ../../default-docker.mk

# This includes the 'help' target that prints out all targets with their descriptions organized by categories
include ../../default-help.mk

.PHONY: all
all: fmt test docker-build docker-push
73 changes: 73 additions & 0 deletions krm-functions/gen-configmap-fn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# gen-configmap-fn

## Overview

<!--mdtogo:Short-->

This function is a simple ConfigMap generator.

<!--mdtogo-->

Application config is often captured in ConfigMaps, but it is difficult to edit
the contents of the ConfigMap with KRM functions. This allows function inputs to
be used to generate ConfigMaps, and those inputs can be more easily edited than
the raw ConfigMap data values.

<!--mdtogo:Long-->

## Usage

To use this function, define a GenConfigMap resource for each ConfigMap you want
to generate. It can be used declaratively or imperatively, but it does require
the GenConfigMap Kind; you cannot run it using a simple ConfigMap for inputs.

When run, it will create or overwrite a ConfigMap, and generate `data` field
entries for each of the listed values in the function config.

### FunctionConfig

```yaml
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: my-generator
configMapMetadata:
name: my-configmap
labels:
foo: bar
params:
hostname: foo.example.com
port: 8992
scheme: https
region: us-east1
data:
- type: literal
key: hello
value: there
- type: gotmpl
key: dburl
value: "{{.scheme}}://{{.hostname}}:{{.port}}/{{.region}}/db"
```
The function config above will generate the following ConfigMap:
```yaml
kind: ConfigMap
metadata:
name: my-configmap
labels:
foo: bar
data:
dburl: https://foo.example.com:8992/us-east1/db
hello: there
```
If `configMapMetadata.name` is not defined, the generated ConfigMap will use the
name of the GenConfigMap resource.

<!--mdtogo-->

## Future Work

This function is very basic right now. Integration with CEL and implementation
of features found in the Kustomize ConfigMapGenerator would be valuable.
135 changes: 135 additions & 0 deletions krm-functions/gen-configmap-fn/fn/gen_configmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
Copyright 2023 Nephio.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fn

import (
"bytes"
"fmt"
"text/template"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)

type GenConfigMapEntry struct {
Type string
Key string
Value string
}

type GenConfigMap struct {
ConfigMapMetadata metav1.ObjectMeta
Params map[string]string
Data []GenConfigMapEntry
}

func (entry *GenConfigMapEntry) generate(params, data map[string]string) error {
switch entry.Type {
case "gotmpl":
t, err := template.New(entry.Key).Parse(entry.Value)
if err != nil {
return err
}
buf := &bytes.Buffer{}
err = t.Execute(buf, params)
if err != nil {
return err
}
data[entry.Key] = buf.String()
default:
data[entry.Key] = entry.Value
}

return nil
}

func (fc *GenConfigMap) Validate() error {
for i, entry := range fc.Data {
if entry.Key == "" {
return fmt.Errorf("data entry %d, key must not be empty", i)
}
}
return nil
}

func Process(rl *fn.ResourceList) (bool, error) {
// read our fc into a new struct
fc := &GenConfigMap{}
err := rl.FunctionConfig.As(fc)
if err != nil {
return false, err
}

name := fc.ConfigMapMetadata.Name
if name == "" {
name = rl.FunctionConfig.GetName()
}

err = fc.Validate()
if err != nil {
return false, err
}

cmko := fn.NewEmptyKubeObject()
err = cmko.SetAPIVersion("v1")
if err != nil {
return false, err
}
err = cmko.SetKind("ConfigMap")
if err != nil {
return false, err
}
err = cmko.SetNestedField(fc.ConfigMapMetadata, "metadata")
if err != nil {
return false, err
}
_, err = cmko.RemoveNestedField("metadata", "creationTimestamp")
if err != nil {
return false, err
}
err = cmko.SetName(name)
if err != nil {
return false, err
}

err = cmko.SetAnnotation(kioutil.PathAnnotation, "_gen_configmap_"+name+".yaml")
if err != nil {
return false, err
}

data := make(map[string]string, len(fc.Data))
for i, entry := range fc.Data {
err = entry.generate(fc.Params, data)
if err != nil {
return false, fmt.Errorf("data entry %d generate error: %s", i, err.Error())
}
}
if len(data) > 0 {
err = cmko.SetNestedField(data, "data")
if err != nil {
return false, err
}
}
err = rl.UpsertObjectToItems(cmko, nil, true)
if err != nil {
return false, err
}

return true, nil
}
80 changes: 80 additions & 0 deletions krm-functions/gen-configmap-fn/fn/gen_configmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright 2023 Nephio.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fn

import (
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/stretchr/testify/assert"
"testing"
)

func TestErrorCases(t *testing.T) {
cases := map[string]struct {
input []byte
errExpected string
}{
"MissingKey": {
input: []byte(`
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: empty-test
data:
- type: literal
value: foo`),
errExpected: "data entry 0, key must not be empty",
},
"GoTemplateParseError": {
input: []byte(`
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: empty-test
data:
- type: gotmpl
key: bad
value: foo{{`),
errExpected: "data entry 0 generate error: template: bad:1: unclosed action",
},
"GoTemplateExecuteError": {
input: []byte(`
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: empty-test
data:
- type: gotmpl
key: bad
value: foo{{template "nope"}}`),
errExpected: "data entry 0 generate error: template: bad:1:14: executing \"bad\" at <{{template \"nope\"}}>: template \"nope\" not defined",
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ko, err := fn.ParseKubeObject(tc.input)
assert.NoError(t, err)

_, err = Process(&fn.ResourceList{FunctionConfig: ko})
if tc.errExpected != "" {
assert.EqualError(t, err, tc.errExpected)
} else {
assert.NoError(t, err)
}
})
}
}
32 changes: 32 additions & 0 deletions krm-functions/gen-configmap-fn/fn/golden_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2023 The Nephio Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fn

import (
"testing"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/testhelpers"
)

const TestDataPath = "testdata"

func TestFunction(t *testing.T) {
// This golden test expects each sub-directory of `testdata` can has its input resources (in `resources.yaml`)
// be modified to the output resources (in `_expected.yaml`).
testhelpers.RunGoldenTests(t, TestDataPath, fn.ResourceListProcessorFunc(Process))
}
14 changes: 14 additions & 0 deletions krm-functions/gen-configmap-fn/fn/testdata/empty/_expected.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: config.kubernetes.io/v1
kind: ResourceList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: empty-test
annotations:
internal.config.kubernetes.io/path: _gen_configmap_empty-test.yaml
functionConfig:
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: empty-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: fn.kpt.dev/v1alpha1
kind: GenConfigMap
metadata:
name: empty-test
Empty file.
Loading

0 comments on commit cf3a496

Please sign in to comment.