arttor/helmify

View on GitHub
pkg/processor/configmap/configmap.go

Summary

Maintainability
A
40 mins
Test Coverage
F
43%
package configmap

import (
    "fmt"
    "github.com/arttor/helmify/pkg/format"
    "io"
    "strings"
    "text/template"

    "github.com/arttor/helmify/pkg/processor"

    "github.com/arttor/helmify/pkg/helmify"
    yamlformat "github.com/arttor/helmify/pkg/yaml"
    "github.com/sirupsen/logrus"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

var configMapTempl, _ = template.New("configMap").Parse(
    `{{ .Meta }}
{{- if .Immutable }}
{{ .Immutable }}
{{- end }}
{{- if .BinaryData }}
{{ .BinaryData }}
{{- end }}
{{- if .Data }}
{{ .Data }}
{{- end }}`)

var configMapGVC = schema.GroupVersionKind{
    Group:   "",
    Version: "v1",
    Kind:    "ConfigMap",
}

// New creates processor for k8s ConfigMap resource.
func New() helmify.Processor {
    return &configMap{}
}

type configMap struct{}

// Process k8s ConfigMap object into template. Returns false if not capable of processing given resource type.
func (d configMap) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructured) (bool, helmify.Template, error) {
    if obj.GroupVersionKind() != configMapGVC {
        return false, nil, nil
    }
    var meta, immutable, binaryData, data string
    meta, err := processor.ProcessObjMeta(appMeta, obj)
    if err != nil {
        return true, nil, err
    }

    if field, exists, _ := unstructured.NestedBool(obj.Object, "immutable"); exists {
        immutable, err = yamlformat.Marshal(map[string]interface{}{"immutable": field}, 0)
        if err != nil {
            return true, nil, err
        }
    }
    if field, exists, _ := unstructured.NestedStringMap(obj.Object, "binaryData"); exists {
        binaryData, err = yamlformat.Marshal(map[string]interface{}{"binaryData": field}, 0)
        if err != nil {
            return true, nil, err
        }
    }

    name := appMeta.TrimName(obj.GetName())
    var values helmify.Values
    if field, exists, _ := unstructured.NestedStringMap(obj.Object, "data"); exists {
        field, values = parseMapData(field, name)
        data, err = yamlformat.Marshal(map[string]interface{}{"data": field}, 0)
        if err != nil {
            return true, nil, err
        }
        data = strings.ReplaceAll(data, "'", "")
    }

    return true, &result{
        name: name + ".yaml",
        data: struct {
            Meta       string
            Immutable  string
            BinaryData string
            Data       string
        }{Meta: meta, Immutable: immutable, BinaryData: binaryData, Data: data},
        values: values,
    }, nil
}

func parseMapData(data map[string]string, configName string) (map[string]string, helmify.Values) {
    values := helmify.Values{}
    for key, value := range data {
        valuesNamePath := []string{configName, key}
        if strings.HasSuffix(key, ".properties") {
            // handle properties
            templated, err := parseProperties(value, valuesNamePath, values)
            if err != nil {
                logrus.WithError(err).Errorf("unable to process configmap data: %v", valuesNamePath)
                continue
            }
            data[key] = templated
            continue
        }
        if strings.Contains(value, "\n") {
            value = format.RemoveTrailingWhitespaces(value)
            templatedVal, err := values.AddYaml(value, 1, false, valuesNamePath...)
            if err != nil {
                logrus.WithError(err).Errorf("unable to process multiline configmap data: %v", valuesNamePath)
                continue
            }
            data[key] = templatedVal
            continue
        }
        // handle plain string
        templatedVal, err := values.Add(value, valuesNamePath...)
        if err != nil {
            logrus.WithError(err).Errorf("unable to process configmap data: %v", valuesNamePath)
            continue
        }
        data[key] = templatedVal
    }
    return data, values
}

// func parseProperties(properties string, path []string, values helmify.Values) (string, error) {
func parseProperties(properties interface{}, path []string, values helmify.Values) (string, error) {
    var res strings.Builder
    for _, line := range strings.Split(strings.TrimSuffix(properties.(string), "\n"), "\n") {
        prop := strings.Split(line, "=")
        if len(prop) != 2 {
            return "", fmt.Errorf("wrong property format in %v: %s", path, line)
        }
        propName, propVal := prop[0], prop[1]
        propNamePath := strings.Split(propName, ".")
        templatedVal, err := values.Add(propVal, append(path, propNamePath...)...)
        if err != nil {
            return "", err
        }
        _, err = res.WriteString(propName + "=" + templatedVal + "\n")
        if err != nil {
            return "", fmt.Errorf("%w: unable to write to string builder", err)
        }
    }
    return res.String(), nil
}

type result struct {
    name string
    data struct {
        Meta       string
        Immutable  string
        BinaryData string
        Data       string
    }
    values helmify.Values
}

func (r *result) Filename() string {
    return r.name
}

func (r *result) Values() helmify.Values {
    return r.values
}

func (r *result) Write(writer io.Writer) error {
    return configMapTempl.Execute(writer, r.data)
}