allow to specify full delimiters without rendering problems

Signed-off-by: Neer Friedman <neerfri@gmail.com>
pull/10299/head
Neer Friedman 4 years ago
parent 4401259fca
commit 68066ed8eb
No known key found for this signature in database
GPG Key ID: 775DBDCAD60A077C

@ -1,6 +1,6 @@
--- ---
# Source: chart-with-alt-delim/templates/alt-configmap.yaml # Source: chart-with-alt-delim/templates/alt-configmap.yaml
# helm: delim=[,] # helm: delimiters=[[,]]
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:

@ -1,4 +1,4 @@
# helm: delim=[,] # helm: delimiters=[[,]]
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:

@ -17,7 +17,6 @@ limitations under the License.
package engine package engine
import ( import (
"bufio"
"fmt" "fmt"
"log" "log"
"path" "path"
@ -84,7 +83,14 @@ func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.C
}.Render(chrt, values) }.Render(chrt, values)
} }
var magicCommentsRegexp = regexp.MustCompile(`^#\s*helm:\s*(?:(\w+)=([^\s]*))*`) // A regexp to match against first lines in a template that start with #
var templateHeaderRegexp = regexp.MustCompile(`(?sm)\A((?:^#\s[^\n]*\n)*)`)
// A regexp to capture a magic comment line in the template header
var magicCommentsRegexp = regexp.MustCompile(`(?m-s)^#\s+helm:\s*(.*)$`)
// The magic comment directive to set the template delimiters
const setDelimitersDirective = "delimiters"
type templateOpts struct { type templateOpts struct {
// Left template delimiter // Left template delimiter
@ -97,6 +103,8 @@ type templateOpts struct {
type renderable struct { type renderable struct {
// tpl is the current template. // tpl is the current template.
tpl string tpl string
// header is the header extracted from the template (first lines starting with #)
header []byte
// vals are the values to be supplied to the template. // vals are the values to be supplied to the template.
vals chartutil.Values vals chartutil.Values
// namespace prefix to the templates of the current chart // namespace prefix to the templates of the current chart
@ -269,6 +277,12 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable)
vals := tpls[filename].vals vals := tpls[filename].vals
vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath}
var buf strings.Builder var buf strings.Builder
// Add template header to output without rendering it through the template engine
if _, err := buf.Write([]byte(tpls[filename].header)); err != nil {
return map[string]string{}, cleanupExecError(filename, err)
}
if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { if err := t.ExecuteTemplate(&buf, filename, vals); err != nil {
return map[string]string{}, cleanupExecError(filename, err) return map[string]string{}, cleanupExecError(filename, err)
} }
@ -389,10 +403,12 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
if !isTemplateValid(c, t.Name) { if !isTemplateValid(c, t.Name) {
continue continue
} }
templateHeader, templateBody := extractTemplateHeaderAndBody(t.Data)
templates[path.Join(newParentID, t.Name)] = renderable{ templates[path.Join(newParentID, t.Name)] = renderable{
tpl: string(t.Data), tpl: string(templateBody),
header: templateHeader,
vals: next, vals: next,
opts: readTemplateMagicComments(string(t.Data)), opts: readTemplateMagicComments(templateHeader),
basePath: path.Join(newParentID, "templates"), basePath: path.Join(newParentID, "templates"),
} }
} }
@ -413,33 +429,31 @@ func isLibraryChart(c *chart.Chart) bool {
return strings.EqualFold(c.Metadata.Type, "library") return strings.EqualFold(c.Metadata.Type, "library")
} }
func readTemplateMagicComments(templateBody string) templateOpts { // extractTemplateHeaderAndBody splits the template to the header and body components of the template
templateOpts := templateOpts{} //
templateHeader := templateHeader(templateBody) // the header is considered all the lines from the template until the first line that does not start with "#"
matches := magicCommentsRegexp.FindAllSubmatch([]byte(templateHeader), -1) // This is used to avoid scanning whole templates with a regular expression when searching for magic comments
for _, match := range matches { // and avoid rendering the header while still keeping it part of the output
if strings.EqualFold(string(match[1]), "delim") { func extractTemplateHeaderAndBody(template []byte) ([]byte, []byte) {
delim := strings.SplitN(string(match[2]), ",", 2) headerBytes := templateHeaderRegexp.Find(template)
templateOpts.delimL = strings.Repeat(delim[0], 2) return headerBytes, template[len(headerBytes):]
templateOpts.delimR = strings.Repeat(delim[1], 2)
}
} }
return templateOpts // readTemplateMagicComments reads magic comments from the template header and returns a `templateOpts` struct
func readTemplateMagicComments(templateHeader []byte) templateOpts {
templateOpts := templateOpts{}
matches := magicCommentsRegexp.FindAllSubmatch(templateHeader, -1)
for _, match := range matches {
magicCommentBody := strings.SplitN(string(match[1]), "=", 2)
if len(magicCommentBody) == 2 && strings.EqualFold(magicCommentBody[0], setDelimitersDirective) {
delimiters := strings.SplitN(magicCommentBody[1], ",", 2)
if len(delimiters) != 2 {
log.Printf("Warning: invalid magic comment: %s", match[1])
continue
} }
templateOpts.delimL = delimiters[0]
// templateHeader returns the lines from the template until the first line that does not start with "#" templateOpts.delimR = delimiters[1]
//
// This is used to avoid scanning whole templates with a regular expression when searching for magic comments.
func templateHeader(templateBody string) string {
var headerBuffer strings.Builder
scanner := bufio.NewScanner(strings.NewReader(templateBody))
for scanner.Scan() {
if strings.HasPrefix(scanner.Text(), "#") {
headerBuffer.WriteString(scanner.Text())
} else {
break
} }
} }
return headerBuffer.String() return templateOpts
} }

Loading…
Cancel
Save