|
1 | 1 | package helm
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bufio" |
| 5 | + "bytes" |
4 | 6 | "fmt"
|
| 7 | + "os" |
5 | 8 | "path"
|
6 | 9 | "path/filepath"
|
7 | 10 | "regexp"
|
@@ -58,6 +61,10 @@ func NewTemplater(
|
58 | 61 |
|
59 | 62 | var releaseNameRegex = regexp.MustCompile("[^a-zA-Z0-9\\-]")
|
60 | 63 |
|
| 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 | + |
61 | 68 | // LocalTemplater implements Templater by using the Commands interface
|
62 | 69 | // from pkg/helm and creating the chart in place
|
63 | 70 | type LocalTemplater struct {
|
@@ -266,6 +273,11 @@ func (f *LocalTemplater) cleanUpAndOutputRenderedFiles(
|
266 | 273 | return errors.Wrap(err, "unable to find tmp rendered chart")
|
267 | 274 | }
|
268 | 275 |
|
| 276 | + err := f.validateGeneratedFiles(f.FS, tempRenderedChartDir) |
| 277 | + if err != nil { |
| 278 | + return errors.Wrapf(err, "unable to validate chart dir") |
| 279 | + } |
| 280 | + |
269 | 281 | debug.Log("event", "readdir", "folder", tempRenderedChartTemplatesDir)
|
270 | 282 | files, err := f.FS.ReadDir(tempRenderedChartTemplatesDir)
|
271 | 283 | if err != nil {
|
@@ -347,3 +359,135 @@ func (f *LocalTemplater) writeStateHelmValuesTo(dest string, defaultValuesPath s
|
347 | 359 |
|
348 | 360 | return nil
|
349 | 361 | }
|
| 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