23
vendor/helm.sh/helm/v3/pkg/engine/doc.go
vendored
Normal file
23
vendor/helm.sh/helm/v3/pkg/engine/doc.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 engine implements the Go text template engine as needed for Helm.
|
||||
|
||||
When Helm renders templates it does so with additional functions and different
|
||||
modes (e.g., strict, lint mode). This package handles the helm specific
|
||||
implementation.
|
||||
*/
|
||||
package engine // import "helm.sh/helm/v3/pkg/engine"
|
||||
392
vendor/helm.sh/helm/v3/pkg/engine/engine.go
vendored
Normal file
392
vendor/helm.sh/helm/v3/pkg/engine/engine.go
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
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 engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
// Engine is an implementation of the Helm rendering implementation for templates.
|
||||
type Engine struct {
|
||||
// If strict is enabled, template rendering will fail if a template references
|
||||
// a value that was not passed in.
|
||||
Strict bool
|
||||
// In LintMode, some 'required' template values may be missing, so don't fail
|
||||
LintMode bool
|
||||
// the rest config to connect to the kubernetes api
|
||||
config *rest.Config
|
||||
}
|
||||
|
||||
// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates.
|
||||
//
|
||||
// Render can be called repeatedly on the same engine.
|
||||
//
|
||||
// This will look in the chart's 'templates' data (e.g. the 'templates/' directory)
|
||||
// and attempt to render the templates there using the values passed in.
|
||||
//
|
||||
// Values are scoped to their templates. A dependency template will not have
|
||||
// access to the values set for its parent. If chart "foo" includes chart "bar",
|
||||
// "bar" will not have access to the values for "foo".
|
||||
//
|
||||
// Values should be prepared with something like `chartutils.ReadValues`.
|
||||
//
|
||||
// Values are passed through the templates according to scope. If the top layer
|
||||
// chart includes the chart foo, which includes the chart bar, the values map
|
||||
// will be examined for a table called "foo". If "foo" is found in vals,
|
||||
// that section of the values will be passed into the "foo" chart. And if that
|
||||
// section contains a value named "bar", that value will be passed on to the
|
||||
// bar chart during render time.
|
||||
func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) {
|
||||
tmap := allTemplates(chrt, values)
|
||||
return e.render(tmap)
|
||||
}
|
||||
|
||||
// Render takes a chart, optional values, and value overrides, and attempts to
|
||||
// render the Go templates using the default options.
|
||||
func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) {
|
||||
return new(Engine).Render(chrt, values)
|
||||
}
|
||||
|
||||
// RenderWithClient takes a chart, optional values, and value overrides, and attempts to
|
||||
// render the Go templates using the default options. This engine is client aware and so can have template
|
||||
// functions that interact with the client
|
||||
func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.Config) (map[string]string, error) {
|
||||
return Engine{
|
||||
config: config,
|
||||
}.Render(chrt, values)
|
||||
}
|
||||
|
||||
// renderable is an object that can be rendered.
|
||||
type renderable struct {
|
||||
// tpl is the current template.
|
||||
tpl string
|
||||
// vals are the values to be supplied to the template.
|
||||
vals chartutil.Values
|
||||
// namespace prefix to the templates of the current chart
|
||||
basePath string
|
||||
}
|
||||
|
||||
const warnStartDelim = "HELM_ERR_START"
|
||||
const warnEndDelim = "HELM_ERR_END"
|
||||
const recursionMaxNums = 1000
|
||||
|
||||
var warnRegex = regexp.MustCompile(warnStartDelim + `(.*)` + warnEndDelim)
|
||||
|
||||
func warnWrap(warn string) string {
|
||||
return warnStartDelim + warn + warnEndDelim
|
||||
}
|
||||
|
||||
// initFunMap creates the Engine's FuncMap and adds context-specific functions.
|
||||
func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) {
|
||||
funcMap := funcMap()
|
||||
includedNames := make(map[string]int)
|
||||
|
||||
// Add the 'include' function here so we can close over t.
|
||||
funcMap["include"] = func(name string, data interface{}) (string, error) {
|
||||
var buf strings.Builder
|
||||
if v, ok := includedNames[name]; ok {
|
||||
if v > recursionMaxNums {
|
||||
return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name)
|
||||
}
|
||||
includedNames[name]++
|
||||
} else {
|
||||
includedNames[name] = 1
|
||||
}
|
||||
err := t.ExecuteTemplate(&buf, name, data)
|
||||
includedNames[name]--
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// Add the 'tpl' function here
|
||||
funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) {
|
||||
basePath, err := vals.PathValue("Template.BasePath")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl)
|
||||
}
|
||||
|
||||
templateName, err := vals.PathValue("Template.Name")
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl)
|
||||
}
|
||||
|
||||
templates := map[string]renderable{
|
||||
templateName.(string): {
|
||||
tpl: tpl,
|
||||
vals: vals,
|
||||
basePath: basePath.(string),
|
||||
},
|
||||
}
|
||||
|
||||
result, err := e.renderWithReferences(templates, referenceTpls)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl)
|
||||
}
|
||||
return result[templateName.(string)], nil
|
||||
}
|
||||
|
||||
// Add the `required` function here so we can use lintMode
|
||||
funcMap["required"] = func(warn string, val interface{}) (interface{}, error) {
|
||||
if val == nil {
|
||||
if e.LintMode {
|
||||
// Don't fail on missing required values when linting
|
||||
log.Printf("[INFO] Missing required value: %s", warn)
|
||||
return "", nil
|
||||
}
|
||||
return val, errors.Errorf(warnWrap(warn))
|
||||
} else if _, ok := val.(string); ok {
|
||||
if val == "" {
|
||||
if e.LintMode {
|
||||
// Don't fail on missing required values when linting
|
||||
log.Printf("[INFO] Missing required value: %s", warn)
|
||||
return "", nil
|
||||
}
|
||||
return val, errors.Errorf(warnWrap(warn))
|
||||
}
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Override sprig fail function for linting and wrapping message
|
||||
funcMap["fail"] = func(msg string) (string, error) {
|
||||
if e.LintMode {
|
||||
// Don't fail when linting
|
||||
log.Printf("[INFO] Fail: %s", msg)
|
||||
return "", nil
|
||||
}
|
||||
return "", errors.New(warnWrap(msg))
|
||||
}
|
||||
|
||||
// If we are not linting and have a cluster connection, provide a Kubernetes-backed
|
||||
// implementation.
|
||||
if !e.LintMode && e.config != nil {
|
||||
funcMap["lookup"] = NewLookupFunction(e.config)
|
||||
}
|
||||
|
||||
t.Funcs(funcMap)
|
||||
}
|
||||
|
||||
// render takes a map of templates/values and renders them.
|
||||
func (e Engine) render(tpls map[string]renderable) (map[string]string, error) {
|
||||
return e.renderWithReferences(tpls, tpls)
|
||||
}
|
||||
|
||||
// renderWithReferences takes a map of templates/values to render, and a map of
|
||||
// templates which can be referenced within them.
|
||||
func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) {
|
||||
// Basically, what we do here is start with an empty parent template and then
|
||||
// build up a list of templates -- one for each file. Once all of the templates
|
||||
// have been parsed, we loop through again and execute every template.
|
||||
//
|
||||
// The idea with this process is to make it possible for more complex templates
|
||||
// to share common blocks, but to make the entire thing feel like a file-based
|
||||
// template engine.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Errorf("rendering template failed: %v", r)
|
||||
}
|
||||
}()
|
||||
t := template.New("gotpl")
|
||||
if e.Strict {
|
||||
t.Option("missingkey=error")
|
||||
} else {
|
||||
// Not that zero will attempt to add default values for types it knows,
|
||||
// but will still emit <no value> for others. We mitigate that later.
|
||||
t.Option("missingkey=zero")
|
||||
}
|
||||
|
||||
e.initFunMap(t, referenceTpls)
|
||||
|
||||
// We want to parse the templates in a predictable order. The order favors
|
||||
// higher-level (in file system) templates over deeply nested templates.
|
||||
keys := sortTemplates(tpls)
|
||||
referenceKeys := sortTemplates(referenceTpls)
|
||||
|
||||
for _, filename := range keys {
|
||||
r := tpls[filename]
|
||||
if _, err := t.New(filename).Parse(r.tpl); err != nil {
|
||||
return map[string]string{}, cleanupParseError(filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the reference templates to the template context
|
||||
// so they can be referenced in the tpl function
|
||||
for _, filename := range referenceKeys {
|
||||
if t.Lookup(filename) == nil {
|
||||
r := referenceTpls[filename]
|
||||
if _, err := t.New(filename).Parse(r.tpl); err != nil {
|
||||
return map[string]string{}, cleanupParseError(filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rendered = make(map[string]string, len(keys))
|
||||
for _, filename := range keys {
|
||||
// Don't render partials. We don't care out the direct output of partials.
|
||||
// They are only included from other templates.
|
||||
if strings.HasPrefix(path.Base(filename), "_") {
|
||||
continue
|
||||
}
|
||||
// At render time, add information about the template that is being rendered.
|
||||
vals := tpls[filename].vals
|
||||
vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath}
|
||||
var buf strings.Builder
|
||||
if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
|
||||
return map[string]string{}, cleanupExecError(filename, err)
|
||||
}
|
||||
|
||||
// Work around the issue where Go will emit "<no value>" even if Options(missing=zero)
|
||||
// is set. Since missing=error will never get here, we do not need to handle
|
||||
// the Strict case.
|
||||
rendered[filename] = strings.ReplaceAll(buf.String(), "<no value>", "")
|
||||
}
|
||||
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func cleanupParseError(filename string, err error) error {
|
||||
tokens := strings.Split(err.Error(), ": ")
|
||||
if len(tokens) == 1 {
|
||||
// This might happen if a non-templating error occurs
|
||||
return fmt.Errorf("parse error in (%s): %s", filename, err)
|
||||
}
|
||||
// The first token is "template"
|
||||
// The second token is either "filename:lineno" or "filename:lineNo:columnNo"
|
||||
location := tokens[1]
|
||||
// The remaining tokens make up a stacktrace-like chain, ending with the relevant error
|
||||
errMsg := tokens[len(tokens)-1]
|
||||
return fmt.Errorf("parse error at (%s): %s", string(location), errMsg)
|
||||
}
|
||||
|
||||
func cleanupExecError(filename string, err error) error {
|
||||
if _, isExecError := err.(template.ExecError); !isExecError {
|
||||
return err
|
||||
}
|
||||
|
||||
tokens := strings.SplitN(err.Error(), ": ", 3)
|
||||
if len(tokens) != 3 {
|
||||
// This might happen if a non-templating error occurs
|
||||
return fmt.Errorf("execution error in (%s): %s", filename, err)
|
||||
}
|
||||
|
||||
// The first token is "template"
|
||||
// The second token is either "filename:lineno" or "filename:lineNo:columnNo"
|
||||
location := tokens[1]
|
||||
|
||||
parts := warnRegex.FindStringSubmatch(tokens[2])
|
||||
if len(parts) >= 2 {
|
||||
return fmt.Errorf("execution error at (%s): %s", string(location), parts[1])
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func sortTemplates(tpls map[string]renderable) []string {
|
||||
keys := make([]string, len(tpls))
|
||||
i := 0
|
||||
for key := range tpls {
|
||||
keys[i] = key
|
||||
i++
|
||||
}
|
||||
sort.Sort(sort.Reverse(byPathLen(keys)))
|
||||
return keys
|
||||
}
|
||||
|
||||
type byPathLen []string
|
||||
|
||||
func (p byPathLen) Len() int { return len(p) }
|
||||
func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] }
|
||||
func (p byPathLen) Less(i, j int) bool {
|
||||
a, b := p[i], p[j]
|
||||
ca, cb := strings.Count(a, "/"), strings.Count(b, "/")
|
||||
if ca == cb {
|
||||
return strings.Compare(a, b) == -1
|
||||
}
|
||||
return ca < cb
|
||||
}
|
||||
|
||||
// allTemplates returns all templates for a chart and its dependencies.
|
||||
//
|
||||
// As it goes, it also prepares the values in a scope-sensitive manner.
|
||||
func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable {
|
||||
templates := make(map[string]renderable)
|
||||
recAllTpls(c, templates, vals)
|
||||
return templates
|
||||
}
|
||||
|
||||
// recAllTpls recurses through the templates in a chart.
|
||||
//
|
||||
// As it recurses, it also sets the values to be appropriate for the template
|
||||
// scope.
|
||||
func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) {
|
||||
next := map[string]interface{}{
|
||||
"Chart": c.Metadata,
|
||||
"Files": newFiles(c.Files),
|
||||
"Release": vals["Release"],
|
||||
"Capabilities": vals["Capabilities"],
|
||||
"Values": make(chartutil.Values),
|
||||
}
|
||||
|
||||
// If there is a {{.Values.ThisChart}} in the parent metadata,
|
||||
// copy that into the {{.Values}} for this template.
|
||||
if c.IsRoot() {
|
||||
next["Values"] = vals["Values"]
|
||||
} else if vs, err := vals.Table("Values." + c.Name()); err == nil {
|
||||
next["Values"] = vs
|
||||
}
|
||||
|
||||
for _, child := range c.Dependencies() {
|
||||
recAllTpls(child, templates, next)
|
||||
}
|
||||
|
||||
newParentID := c.ChartFullPath()
|
||||
for _, t := range c.Templates {
|
||||
if !isTemplateValid(c, t.Name) {
|
||||
continue
|
||||
}
|
||||
templates[path.Join(newParentID, t.Name)] = renderable{
|
||||
tpl: string(t.Data),
|
||||
vals: next,
|
||||
basePath: path.Join(newParentID, "templates"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isTemplateValid returns true if the template is valid for the chart type
|
||||
func isTemplateValid(ch *chart.Chart, templateName string) bool {
|
||||
if isLibraryChart(ch) {
|
||||
return strings.HasPrefix(filepath.Base(templateName), "_")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isLibraryChart returns true if the chart is a library chart
|
||||
func isLibraryChart(c *chart.Chart) bool {
|
||||
return strings.EqualFold(c.Metadata.Type, "library")
|
||||
}
|
||||
160
vendor/helm.sh/helm/v3/pkg/engine/files.go
vendored
Normal file
160
vendor/helm.sh/helm/v3/pkg/engine/files.go
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
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 engine
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// files is a map of files in a chart that can be accessed from a template.
|
||||
type files map[string][]byte
|
||||
|
||||
// NewFiles creates a new files from chart files.
|
||||
// Given an []*chart.File (the format for files in a chart.Chart), extract a map of files.
|
||||
func newFiles(from []*chart.File) files {
|
||||
files := make(map[string][]byte)
|
||||
for _, f := range from {
|
||||
files[f.Name] = f.Data
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
// GetBytes gets a file by path.
|
||||
//
|
||||
// The returned data is raw. In a template context, this is identical to calling
|
||||
// {{index .Files $path}}.
|
||||
//
|
||||
// This is intended to be accessed from within a template, so a missed key returns
|
||||
// an empty []byte.
|
||||
func (f files) GetBytes(name string) []byte {
|
||||
if v, ok := f[name]; ok {
|
||||
return v
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// Get returns a string representation of the given file.
|
||||
//
|
||||
// Fetch the contents of a file as a string. It is designed to be called in a
|
||||
// template.
|
||||
//
|
||||
// {{.Files.Get "foo"}}
|
||||
func (f files) Get(name string) string {
|
||||
return string(f.GetBytes(name))
|
||||
}
|
||||
|
||||
// Glob takes a glob pattern and returns another files object only containing
|
||||
// matched files.
|
||||
//
|
||||
// This is designed to be called from a template.
|
||||
//
|
||||
// {{ range $name, $content := .Files.Glob("foo/**") }}
|
||||
// {{ $name }}: |
|
||||
// {{ .Files.Get($name) | indent 4 }}{{ end }}
|
||||
func (f files) Glob(pattern string) files {
|
||||
g, err := glob.Compile(pattern, '/')
|
||||
if err != nil {
|
||||
g, _ = glob.Compile("**")
|
||||
}
|
||||
|
||||
nf := newFiles(nil)
|
||||
for name, contents := range f {
|
||||
if g.Match(name) {
|
||||
nf[name] = contents
|
||||
}
|
||||
}
|
||||
|
||||
return nf
|
||||
}
|
||||
|
||||
// AsConfig turns a Files group and flattens it to a YAML map suitable for
|
||||
// including in the 'data' section of a Kubernetes ConfigMap definition.
|
||||
// Duplicate keys will be overwritten, so be aware that your file names
|
||||
// (regardless of path) should be unique.
|
||||
//
|
||||
// This is designed to be called from a template, and will return empty string
|
||||
// (via toYAML function) if it cannot be serialized to YAML, or if the Files
|
||||
// object is nil.
|
||||
//
|
||||
// The output will not be indented, so you will want to pipe this to the
|
||||
// 'indent' template function.
|
||||
//
|
||||
// data:
|
||||
// {{ .Files.Glob("config/**").AsConfig() | indent 4 }}
|
||||
func (f files) AsConfig() string {
|
||||
if f == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
m := make(map[string]string)
|
||||
|
||||
// Explicitly convert to strings, and file names
|
||||
for k, v := range f {
|
||||
m[path.Base(k)] = string(v)
|
||||
}
|
||||
|
||||
return toYAML(m)
|
||||
}
|
||||
|
||||
// AsSecrets returns the base64-encoded value of a Files object suitable for
|
||||
// including in the 'data' section of a Kubernetes Secret definition.
|
||||
// Duplicate keys will be overwritten, so be aware that your file names
|
||||
// (regardless of path) should be unique.
|
||||
//
|
||||
// This is designed to be called from a template, and will return empty string
|
||||
// (via toYAML function) if it cannot be serialized to YAML, or if the Files
|
||||
// object is nil.
|
||||
//
|
||||
// The output will not be indented, so you will want to pipe this to the
|
||||
// 'indent' template function.
|
||||
//
|
||||
// data:
|
||||
// {{ .Files.Glob("secrets/*").AsSecrets() }}
|
||||
func (f files) AsSecrets() string {
|
||||
if f == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
m := make(map[string]string)
|
||||
|
||||
for k, v := range f {
|
||||
m[path.Base(k)] = base64.StdEncoding.EncodeToString(v)
|
||||
}
|
||||
|
||||
return toYAML(m)
|
||||
}
|
||||
|
||||
// Lines returns each line of a named file (split by "\n") as a slice, so it can
|
||||
// be ranged over in your templates.
|
||||
//
|
||||
// This is designed to be called from a template.
|
||||
//
|
||||
// {{ range .Files.Lines "foo/bar.html" }}
|
||||
// {{ . }}{{ end }}
|
||||
func (f files) Lines(path string) []string {
|
||||
if f == nil || f[path] == nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return strings.Split(string(f[path]), "\n")
|
||||
}
|
||||
177
vendor/helm.sh/helm/v3/pkg/engine/funcs.go
vendored
Normal file
177
vendor/helm.sh/helm/v3/pkg/engine/funcs.go
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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 engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// funcMap returns a mapping of all of the functions that Engine has.
|
||||
//
|
||||
// Because some functions are late-bound (e.g. contain context-sensitive
|
||||
// data), the functions may not all perform identically outside of an Engine
|
||||
// as they will inside of an Engine.
|
||||
//
|
||||
// Known late-bound functions:
|
||||
//
|
||||
// - "include"
|
||||
// - "tpl"
|
||||
//
|
||||
// These are late-bound in Engine.Render(). The
|
||||
// version included in the FuncMap is a placeholder.
|
||||
//
|
||||
func funcMap() template.FuncMap {
|
||||
f := sprig.TxtFuncMap()
|
||||
delete(f, "env")
|
||||
delete(f, "expandenv")
|
||||
|
||||
// Add some extra functionality
|
||||
extra := template.FuncMap{
|
||||
"toToml": toTOML,
|
||||
"toYaml": toYAML,
|
||||
"fromYaml": fromYAML,
|
||||
"fromYamlArray": fromYAMLArray,
|
||||
"toJson": toJSON,
|
||||
"fromJson": fromJSON,
|
||||
"fromJsonArray": fromJSONArray,
|
||||
|
||||
// This is a placeholder for the "include" function, which is
|
||||
// late-bound to a template. By declaring it here, we preserve the
|
||||
// integrity of the linter.
|
||||
"include": func(string, interface{}) string { return "not implemented" },
|
||||
"tpl": func(string, interface{}) interface{} { return "not implemented" },
|
||||
"required": func(string, interface{}) (interface{}, error) { return "not implemented", nil },
|
||||
// Provide a placeholder for the "lookup" function, which requires a kubernetes
|
||||
// connection.
|
||||
"lookup": func(string, string, string, string) (map[string]interface{}, error) {
|
||||
return map[string]interface{}{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range extra {
|
||||
f[k] = v
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
124
vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go
vendored
Normal file
124
vendor/helm.sh/helm/v3/pkg/engine/lookup_func.go
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
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 engine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error)
|
||||
|
||||
// NewLookupFunction returns a function for looking up objects in the cluster.
|
||||
//
|
||||
// If the resource does not exist, no error is raised.
|
||||
//
|
||||
// This function is considered deprecated, and will be renamed in Helm 4. It will no
|
||||
// longer be a public function.
|
||||
func NewLookupFunction(config *rest.Config) lookupFunc {
|
||||
return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) {
|
||||
var client dynamic.ResourceInterface
|
||||
c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config)
|
||||
if err != nil {
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
if namespaced && namespace != "" {
|
||||
client = c.Namespace(namespace)
|
||||
} else {
|
||||
client = c
|
||||
}
|
||||
if name != "" {
|
||||
// this will return a single object
|
||||
obj, err := client.Get(context.Background(), name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Just return an empty interface when the object was not found.
|
||||
// That way, users can use `if not (lookup ...)` in their templates.
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
return obj.UnstructuredContent(), nil
|
||||
}
|
||||
//this will return a list
|
||||
obj, err := client.List(context.Background(), metav1.ListOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Just return an empty interface when the object was not found.
|
||||
// That way, users can use `if not (lookup ...)` in their templates.
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
return map[string]interface{}{}, err
|
||||
}
|
||||
return obj.UnstructuredContent(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// getDynamicClientOnUnstructured returns a dynamic client on an Unstructured type. This client can be further namespaced.
|
||||
func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) {
|
||||
gvk := schema.FromAPIVersionAndKind(apiversion, kind)
|
||||
apiRes, err := getAPIResourceForGVK(gvk, config)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] unable to get apiresource from unstructured: %s , error %s", gvk.String(), err)
|
||||
return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String())
|
||||
}
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: apiRes.Group,
|
||||
Version: apiRes.Version,
|
||||
Resource: apiRes.Name,
|
||||
}
|
||||
intf, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] unable to get dynamic client %s", err)
|
||||
return nil, false, err
|
||||
}
|
||||
res := intf.Resource(gvr)
|
||||
return res, apiRes.Namespaced, nil
|
||||
}
|
||||
|
||||
func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (metav1.APIResource, error) {
|
||||
res := metav1.APIResource{}
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] unable to create discovery client %s", err)
|
||||
return res, err
|
||||
}
|
||||
resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] unable to retrieve resource list for: %s , error: %s", gvk.GroupVersion().String(), err)
|
||||
return res, err
|
||||
}
|
||||
for _, resource := range resList.APIResources {
|
||||
//if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now.
|
||||
if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") {
|
||||
res = resource
|
||||
res.Group = gvk.Group
|
||||
res.Version = gvk.Version
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user