From b6f27ecd57c36c7117039bf6120f4c5ebb640050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=A4hler?= Date: Wed, 12 Jun 2024 14:27:41 +0200 Subject: [PATCH] feat(controller): extended functionmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oliver Bähler --- controllers/funcmap.go | 178 +++++++++++++++++++++++ controllers/handlers_kustomize.go | 3 +- controllers/template_instantiation.go | 3 +- controllers/templateresourcedef_utils.go | 3 +- 4 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 controllers/funcmap.go diff --git a/controllers/funcmap.go b/controllers/funcmap.go new file mode 100644 index 00000000..511d978f --- /dev/null +++ b/controllers/funcmap.go @@ -0,0 +1,178 @@ +/* +Copyright 2024. projectsveltos.io. All rights reserved. + +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. +*/ + +/* +Copyright The Helm 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 controllers + +import ( + "bytes" + "encoding/json" + "strings" + "text/template" + + "github.com/BurntSushi/toml" + "github.com/Masterminds/sprig/v3" + "gopkg.in/yaml.v3" +) + +// TxtFuncMap returns an aggregated template function map. Currently (custom functions + sprig) +func ExtraFuncMap() template.FuncMap { + funcMap := sprig.FuncMap() + + extraFuncs := template.FuncMap{ + "toToml": toTOML, + "fromToml": fromTOML, + "toYaml": toYAML, + "fromYaml": fromYAML, + "fromYamlArray": fromYAMLArray, + "toJson": toJSON, + "fromJson": fromJSON, + "fromJsonArray": fromJSONArray, + } + + for k, v := range extraFuncs { + funcMap[k] = v + } + + return funcMap +} + +// toYAML takes an interface, marshals it to yaml, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toYAML(v interface{}) string { + data, err := yaml.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return strings.TrimSuffix(string(data), "\n") +} + +// fromYAML converts a YAML document into a map[string]interface{}. +// +// This is not a general-purpose YAML parser, and will not parse all valid +// YAML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromYAML(str string) map[string]interface{} { + m := map[string]interface{}{} + + if err := yaml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// fromYAMLArray converts a YAML array into a []interface{}. +// +// This is not a general-purpose YAML parser, and will not parse all valid +// YAML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string as +// the first and only item in the returned array. +func fromYAMLArray(str string) []interface{} { + a := []interface{}{} + + if err := yaml.Unmarshal([]byte(str), &a); err != nil { + a = []interface{}{err.Error()} + } + return a +} + +// toTOML takes an interface, marshals it to toml, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toTOML(v interface{}) string { + b := bytes.NewBuffer(nil) + e := toml.NewEncoder(b) + err := e.Encode(v) + if err != nil { + return err.Error() + } + return b.String() +} + +// fromTOML converts a TOML document into a map[string]interface{}. +// +// This is not a general-purpose TOML parser, and will not parse all valid +// TOML documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromTOML(str string) map[string]interface{} { + m := make(map[string]interface{}) + + if err := toml.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// toJSON takes an interface, marshals it to json, and returns a string. It will +// always return a string, even on marshal error (empty string). +// +// This is designed to be called from a template. +func toJSON(v interface{}) string { + data, err := json.Marshal(v) + if err != nil { + // Swallow errors inside of a template. + return "" + } + return string(data) +} + +// fromJSON converts a JSON document into a map[string]interface{}. +// +// This is not a general-purpose JSON parser, and will not parse all valid +// JSON documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string into +// m["Error"] in the returned map. +func fromJSON(str string) map[string]interface{} { + m := make(map[string]interface{}) + + if err := json.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} + +// fromJSONArray converts a JSON array into a []interface{}. +// +// This is not a general-purpose JSON parser, and will not parse all valid +// JSON documents. Additionally, because its intended use is within templates +// it tolerates errors. It will insert the returned error message string as +// the first and only item in the returned array. +func fromJSONArray(str string) []interface{} { + a := []interface{}{} + + if err := json.Unmarshal([]byte(str), &a); err != nil { + a = []interface{}{err.Error()} + } + return a +} diff --git a/controllers/handlers_kustomize.go b/controllers/handlers_kustomize.go index b3e36912..b5bd7dc5 100644 --- a/controllers/handlers_kustomize.go +++ b/controllers/handlers_kustomize.go @@ -30,7 +30,6 @@ import ( "strings" "text/template" - "github.com/Masterminds/sprig/v3" sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/gdexlab/go-render/render" "github.com/go-logr/logr" @@ -863,7 +862,7 @@ func extractTarGz(src, dest string) error { func instantiateResourceWithSubstituteValues(templateName string, resource []byte, substituteValues map[string]string, logger logr.Logger) ([]byte, error) { - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(sprig.FuncMap()).Parse(string(resource)) + tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(ExtraFuncMap()).Parse(string(resource)) if err != nil { return nil, err } diff --git a/controllers/template_instantiation.go b/controllers/template_instantiation.go index 2bec6d58..9fdceb90 100644 --- a/controllers/template_instantiation.go +++ b/controllers/template_instantiation.go @@ -19,7 +19,6 @@ import ( "fmt" "text/template" - "github.com/Masterminds/sprig/v3" "github.com/go-logr/logr" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -172,7 +171,7 @@ func instantiateTemplateValues(ctx context.Context, config *rest.Config, c clien } templateName := getTemplateName(clusterNamespace, clusterName, requestorName) - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(sprig.FuncMap()).Parse(values) + tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(ExtraFuncMap()).Parse(values) if err != nil { return "", err } diff --git a/controllers/templateresourcedef_utils.go b/controllers/templateresourcedef_utils.go index 785ac04d..253d8511 100644 --- a/controllers/templateresourcedef_utils.go +++ b/controllers/templateresourcedef_utils.go @@ -21,7 +21,6 @@ import ( "context" "text/template" - "github.com/Masterminds/sprig/v3" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -52,7 +51,7 @@ func getTemplateResourceName(clusterSummary *configv1alpha1.ClusterSummary, // Accept name that are templates templateName := getTemplateName(clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, string(clusterSummary.Spec.ClusterType)) - tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(sprig.FuncMap()).Parse(ref.Resource.Name) + tmpl, err := template.New(templateName).Option("missingkey=error").Funcs(ExtraFuncMap()).Parse(ref.Resource.Name) if err != nil { return "", err }