Skip to content
This repository was archived by the owner on Mar 24, 2023. It is now read-only.

Commit e0bc575

Browse files
authored
Merge pull request #636 from laverya/laverya/fix-nil-kustomize-errors
fix empty 'env', 'args' and 'value' entries in generated helm yaml
2 parents bf1f373 + 8ec6b6b commit e0bc575

File tree

2 files changed

+439
-0
lines changed

2 files changed

+439
-0
lines changed

Diff for: pkg/lifecycle/render/helm/template.go

+144
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package helm
22

33
import (
4+
"bufio"
5+
"bytes"
46
"fmt"
7+
"os"
58
"path"
69
"path/filepath"
710
"regexp"
@@ -58,6 +61,10 @@ func NewTemplater(
5861

5962
var releaseNameRegex = regexp.MustCompile("[^a-zA-Z0-9\\-]")
6063

64+
var argsLineRegex = regexp.MustCompile(`^\s*args:\s*$`)
65+
var envLineRegex = regexp.MustCompile(`^\s*env:\s*$`)
66+
var valueLineRegex = regexp.MustCompile(`^\s*value:\s*$`)
67+
6168
// LocalTemplater implements Templater by using the Commands interface
6269
// from pkg/helm and creating the chart in place
6370
type LocalTemplater struct {
@@ -266,6 +273,11 @@ func (f *LocalTemplater) cleanUpAndOutputRenderedFiles(
266273
return errors.Wrap(err, "unable to find tmp rendered chart")
267274
}
268275

276+
err := f.validateGeneratedFiles(f.FS, tempRenderedChartDir)
277+
if err != nil {
278+
return errors.Wrapf(err, "unable to validate chart dir")
279+
}
280+
269281
debug.Log("event", "readdir", "folder", tempRenderedChartTemplatesDir)
270282
files, err := f.FS.ReadDir(tempRenderedChartTemplatesDir)
271283
if err != nil {
@@ -347,3 +359,135 @@ func (f *LocalTemplater) writeStateHelmValuesTo(dest string, defaultValuesPath s
347359

348360
return nil
349361
}
362+
363+
// validate each file to make sure that it conforms to the yaml spec
364+
// TODO replace this with an actual validation tool
365+
func (f *LocalTemplater) validateGeneratedFiles(
366+
fs afero.Afero,
367+
dir string,
368+
) error {
369+
debug := level.Debug(log.With(f.Logger, "method", "validateGeneratedFiles"))
370+
371+
debug.Log("event", "readdir", "folder", dir)
372+
files, err := fs.ReadDir(dir)
373+
if err != nil {
374+
debug.Log("event", "readdir.fail", "folder", dir)
375+
return errors.Wrapf(err, "failed to read folder %s", dir)
376+
}
377+
378+
for _, file := range files {
379+
thisPath := filepath.Join(dir, file.Name())
380+
if file.IsDir() {
381+
err := f.validateGeneratedFiles(fs, thisPath)
382+
if err != nil {
383+
return err
384+
}
385+
} else {
386+
err := fixFile(fs, thisPath, file.Mode())
387+
if err != nil {
388+
return err
389+
}
390+
}
391+
}
392+
393+
return nil
394+
}
395+
396+
func fixFile(fs afero.Afero, thisPath string, mode os.FileMode) error {
397+
contents, err := fs.ReadFile(thisPath)
398+
if err != nil {
399+
return errors.Wrapf(err, "failed to read file %s", thisPath)
400+
}
401+
402+
scanner := bufio.NewScanner(bytes.NewReader(contents))
403+
404+
lines := []string{}
405+
for scanner.Scan() {
406+
lines = append(lines, scanner.Text())
407+
}
408+
if err := scanner.Err(); err != nil {
409+
return errors.Wrapf(err, "failed to read lines from file %s", thisPath)
410+
}
411+
412+
lines = fixLines(lines)
413+
414+
var outputFile bytes.Buffer
415+
for idx, line := range lines {
416+
if idx+1 != len(lines) || contents[len(contents)-1] == '\n' {
417+
fmt.Fprintln(&outputFile, line)
418+
} else {
419+
// avoid adding trailing newlines
420+
fmt.Fprintf(&outputFile, line)
421+
}
422+
}
423+
424+
err = fs.WriteFile(thisPath, outputFile.Bytes(), mode)
425+
if err != nil {
426+
return errors.Wrapf(err, "failed to write file %s after fixup", thisPath)
427+
}
428+
429+
return nil
430+
}
431+
432+
// applies all fixes to all lines provided
433+
func fixLines(lines []string) []string {
434+
for idx, line := range lines {
435+
if argsLineRegex.MatchString(line) {
436+
// line has `args:` and nothing else but whitespace
437+
if !checkIsChild(line, nextLine(idx, lines)) {
438+
// next line is not a child, so args has no contents, add an empty array
439+
lines[idx] = line + " []"
440+
}
441+
} else if envLineRegex.MatchString(line) {
442+
// line has `env:` and nothing else but whitespace
443+
if !checkIsChild(line, nextLine(idx, lines)) {
444+
// next line is not a child, so env has no contents, add an empty object
445+
lines[idx] = line + " {}"
446+
}
447+
} else if valueLineRegex.MatchString(line) {
448+
// line has `value:` and nothing else but whitespace
449+
if !checkIsChild(line, nextLine(idx, lines)) {
450+
// next line is not a child, so value has no contents, add an empty string
451+
lines[idx] = line + ` ""`
452+
}
453+
}
454+
}
455+
456+
return lines
457+
}
458+
459+
// returns true if the second line is a child of the first
460+
func checkIsChild(firstLine, secondLine string) bool {
461+
cutset := fmt.Sprintf(" \t")
462+
firstIndentation := len(firstLine) - len(strings.TrimLeft(firstLine, cutset))
463+
secondIndentation := len(secondLine) - len(strings.TrimLeft(secondLine, cutset))
464+
465+
if firstIndentation < secondIndentation {
466+
// if the next line is more indented, it's a child
467+
return true
468+
}
469+
470+
if firstIndentation == secondIndentation {
471+
if secondLine[secondIndentation] == '-' {
472+
// if the next line starts with '-' and is on the same indentation, it's a child
473+
return true
474+
}
475+
}
476+
477+
return false
478+
}
479+
480+
// returns the next line after idx that is not entirely whitespace or a comment. If there are no lines meeting these criteria, returns ""
481+
func nextLine(idx int, lines []string) string {
482+
if idx+1 >= len(lines) {
483+
return ""
484+
}
485+
486+
if len(strings.TrimSpace(lines[idx+1])) > 0 {
487+
if strings.TrimSpace(lines[idx+1])[0] != '#' {
488+
return lines[idx+1]
489+
}
490+
}
491+
492+
return nextLine(idx+1, lines)
493+
}

0 commit comments

Comments
 (0)