Skip to content

Commit

Permalink
New flag --document-dependency-values
Browse files Browse the repository at this point in the history
Adds new CLI flag -u/--document-dependency-values.

The flag is disabled by default, so it should not affect existing behavior.

**This change also includes two drive-by bug fixes!**

* Replace usages of `path.Join` with `filepath.Join` when joining directories, for better cross-platform support.
* Fix a `FileSortOrder` sorting bug in `model.go` where it would not sort by line due to an incorrect `valuesTableRows[i].LineNumber < valuesTableRows[i].LineNumber` comparison operation.
  • Loading branch information
armsnyder committed Apr 3, 2022
1 parent a126ae1 commit 3d463f7
Show file tree
Hide file tree
Showing 18 changed files with 450 additions and 86 deletions.
1 change: 1 addition & 0 deletions cmd/helm-docs/command_line.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func newHelmDocsCommand(run func(cmd *cobra.Command, args []string)) (*cobra.Com
command.PersistentFlags().StringSliceP("template-files", "t", []string{"README.md.gotmpl"}, "gotemplate file paths relative to each chart directory from which documentation will be generated")
command.PersistentFlags().StringP("badge-style", "b", "flat-square", "badge style to use for charts")
command.PersistentFlags().StringP("values-file", "f", "values.yaml", "Path to values file")
command.PersistentFlags().BoolP("document-dependency-values", "u", false, "For charts with dependencies, include the dependency values in the chart values documentation")

viper.AutomaticEnv()
viper.SetEnvPrefix("HELM_DOCS")
Expand Down
118 changes: 91 additions & 27 deletions cmd/helm-docs/main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package main

import (
"fmt"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync"

Expand All @@ -14,65 +18,125 @@ import (
"github.com/norwoodj/helm-docs/pkg/helm"
)

func retrieveInfoAndPrintDocumentation(chartDirectory string, chartSearchRoot string, templateFiles []string, waitGroup *sync.WaitGroup, badgeStyle string, dryRun bool) {
defer waitGroup.Done()
chartDocumentationInfo, err := helm.ParseChartInformation(path.Join(chartSearchRoot, chartDirectory))

if err != nil {
log.Warnf("Error parsing information for chart %s, skipping: %s", chartDirectory, err)
return
// parallelProcessIterable runs the visitFn function on each element of the iterable, using
// parallelism number of worker goroutines. The iterable may be a slice or a map. In the case of a
// map, the argument passed to visitFn will be the key.
func parallelProcessIterable(iterable interface{}, parallelism int, visitFn func(elem interface{})) {
workChan := make(chan interface{})

wg := &sync.WaitGroup{}
wg.Add(parallelism)

for i := 0; i < parallelism; i++ {
go func() {
defer wg.Done()
for elem := range workChan {
visitFn(elem)
}
}()
}

document.PrintDocumentation(chartDocumentationInfo, chartSearchRoot, templateFiles, dryRun, version, badgeStyle)
iterableValue := reflect.ValueOf(iterable)

}
if iterableValue.Kind() == reflect.Map {
for _, key := range iterableValue.MapKeys() {
workChan <- key.Interface()
}
} else {
sliceLen := iterableValue.Len()
for i := 0; i < sliceLen; i++ {
workChan <- iterableValue.Index(i).Interface()
}
}

func helmDocs(_ *cobra.Command, _ []string) {
initializeCli()
close(workChan)
wg.Wait()
}

chartSearchRoot := viper.GetString("chart-search-root")
func readDocumentationInfoByChartPath(chartSearchRoot string, parallelism int) (map[string]helm.ChartDocumentationInfo, error) {
var fullChartSearchRoot string

if path.IsAbs(chartSearchRoot) {
fullChartSearchRoot = chartSearchRoot
} else {
cwd, err := os.Getwd()
if err != nil {
log.Warnf("Error getting working directory: %s", err)
return
return nil, fmt.Errorf("error getting working directory: %w", err)
}

fullChartSearchRoot = path.Join(cwd, chartSearchRoot)
fullChartSearchRoot = filepath.Join(cwd, chartSearchRoot)
}

chartDirs, err := helm.FindChartDirectories(fullChartSearchRoot)
if err != nil {
log.Errorf("Error finding chart directories: %s", err)
os.Exit(1)
return nil, fmt.Errorf("error finding chart directories: %w", err)
}

log.Infof("Found Chart directories [%s]", strings.Join(chartDirs, ", "))

templateFiles := viper.GetStringSlice("template-files")
log.Debugf("Rendering from optional template files [%s]", strings.Join(templateFiles, ", "))

documentationInfoByChartPath := make(map[string]helm.ChartDocumentationInfo, len(chartDirs))
documentationInfoByChartPathMu := &sync.Mutex{}

parallelProcessIterable(chartDirs, parallelism, func(elem interface{}) {
chartDir := elem.(string)
info, err := helm.ParseChartInformation(filepath.Join(chartSearchRoot, chartDir))
if err != nil {
log.Warnf("Error parsing information for chart %s, skipping: %s", chartDir, err)
return
}
documentationInfoByChartPathMu.Lock()
documentationInfoByChartPath[info.ChartDirectory] = info
documentationInfoByChartPathMu.Unlock()
})

return documentationInfoByChartPath, nil
}

func writeDocumentation(chartSearchRoot string, documentationInfoByChartPath map[string]helm.ChartDocumentationInfo, dryRun bool, parallelism int) {
templateFiles := viper.GetStringSlice("template-files")
badgeStyle := viper.GetString("badge-style")

log.Debugf("Rendering from optional template files [%s]", strings.Join(templateFiles, ", "))

documentDependencyValues := viper.GetBool("document-dependency-values")

parallelProcessIterable(documentationInfoByChartPath, parallelism, func(elem interface{}) {
info := documentationInfoByChartPath[elem.(string)]
var err error
var dependencyValues []document.DependencyValues
if documentDependencyValues {
dependencyValues, err = document.GetDependencyValues(info, documentationInfoByChartPath)
if err != nil {
log.Warnf("Error evaluating dependency values for chart %s, skipping: %v", info.ChartDirectory, err)
return
}
}
document.PrintDocumentation(info, chartSearchRoot, templateFiles, dryRun, version, badgeStyle, dependencyValues)
})
}

func helmDocs(_ *cobra.Command, _ []string) {
initializeCli()

chartSearchRoot := viper.GetString("chart-search-root")
dryRun := viper.GetBool("dry-run")
waitGroup := sync.WaitGroup{}

for _, c := range chartDirs {
waitGroup.Add(1)
parallelism := runtime.NumCPU() * 2

// On dry runs all output goes to stdout, and so as to not jumble things, generate serially
if dryRun {
retrieveInfoAndPrintDocumentation(c, fullChartSearchRoot, templateFiles, &waitGroup, badgeStyle, dryRun)
} else {
go retrieveInfoAndPrintDocumentation(c, fullChartSearchRoot, templateFiles, &waitGroup, badgeStyle, dryRun)
}
// On dry runs all output goes to stdout, and so as to not jumble things, generate serially.
if dryRun {
parallelism = 1
}

documentationInfoByChartPath, err := readDocumentationInfoByChartPath(chartSearchRoot, parallelism)
if err != nil {
log.Fatal(err)
}

waitGroup.Wait()
writeDocumentation(chartSearchRoot, documentationInfoByChartPath, dryRun, parallelism)
}

func main() {
Expand Down
10 changes: 10 additions & 0 deletions example-charts/umbrella/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v2
name: umbrella
description: A chart demonstrating that values documentation from child charts are aggregated on the parent chart.
version: "0.1.0"
type: application
dependencies:
- name: sub-a
version: 0.1.0
- name: sub-b
version: 0.1.0
22 changes: 22 additions & 0 deletions example-charts/umbrella/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# umbrella

![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)

A chart demonstrating that values documentation from child charts are aggregated on the parent chart.

## Requirements

| Repository | Name | Version |
|------------|------|---------|
| | sub-a | 0.1.0 |
| | sub-b | 0.1.0 |

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| global.myGlobalKey | string | `"my-global-value"` | A global key |
| myParentKey | string | `"my-parent-value"` | A parent key |
| sub-a.mySubKeyA | string | `"my-sub-value-a"` | Value for sub-chart A |
| sub-b.mySubKeyB | string | `"my-sub-value-b"` | Value for sub-chart B |

5 changes: 5 additions & 0 deletions example-charts/umbrella/charts/sub-a/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v2
name: sub-a
description: A sub-chart.
version: "0.1.0"
type: application
13 changes: 13 additions & 0 deletions example-charts/umbrella/charts/sub-a/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# sub-a

![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)

A sub-chart.

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| global.myGlobalKey | string | `"my-global-value"` | A global key |
| mySubKeyA | string | `"my-sub-value-a"` | Value for sub-chart A |

6 changes: 6 additions & 0 deletions example-charts/umbrella/charts/sub-a/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
global:
# -- A global key
myGlobalKey: my-global-value

# -- Value for sub-chart A
mySubKeyA: my-sub-value-a
5 changes: 5 additions & 0 deletions example-charts/umbrella/charts/sub-b/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v2
name: sub-b
description: A sub-chart.
version: "0.1.0"
type: application
13 changes: 13 additions & 0 deletions example-charts/umbrella/charts/sub-b/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# sub-b

![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)

A sub-chart.

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| global.myGlobalKey | string | `"my-global-value"` | A global key |
| mySubKeyB | string | `"my-sub-value-b"` | Value for sub-chart B |

6 changes: 6 additions & 0 deletions example-charts/umbrella/charts/sub-b/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
global:
# -- A global key
myGlobalKey: my-global-value

# -- Value for sub-chart B
mySubKeyB: my-sub-value-b
6 changes: 6 additions & 0 deletions example-charts/umbrella/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
global:
# -- A global key
myGlobalKey: my-global-value

# -- A parent key
myParentKey: my-parent-value
62 changes: 62 additions & 0 deletions pkg/document/dependency_values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package document

import (
"errors"
"fmt"
"path/filepath"

"gopkg.in/yaml.v3"

"github.com/norwoodj/helm-docs/pkg/helm"
)

type DependencyValues struct {
Prefix string
ChartValues *yaml.Node
ChartValuesDescriptions map[string]helm.ChartValueDescription
}

func GetDependencyValues(root helm.ChartDocumentationInfo, allChartInfoByChartPath map[string]helm.ChartDocumentationInfo) ([]DependencyValues, error) {
return getDependencyValuesWithPrefix(root, allChartInfoByChartPath, "")
}

func getDependencyValuesWithPrefix(root helm.ChartDocumentationInfo, allChartInfoByChartPath map[string]helm.ChartDocumentationInfo, prefix string) ([]DependencyValues, error) {
if len(root.Dependencies) == 0 {
return nil, nil
}

result := make([]DependencyValues, 0, len(root.Dependencies))

for _, dep := range root.Dependencies {
if dep.Repository != "" {
return nil, errors.New("remote dependencies are not yet supported")
}

searchPath := filepath.Join(root.ChartDirectory, "charts", dep.Name)
depInfo, ok := allChartInfoByChartPath[searchPath]
if !ok {
return nil, fmt.Errorf("dependency with path %q was not found", searchPath)
}

alias := dep.Alias
if alias == "" {
alias = dep.Name
}
depPrefix := prefix + alias

result = append(result, DependencyValues{
Prefix: depPrefix,
ChartValues: depInfo.ChartValues,
ChartValuesDescriptions: depInfo.ChartValuesDescriptions,
})

children, err := getDependencyValuesWithPrefix(depInfo, allChartInfoByChartPath, depPrefix+".")
if err != nil {
return nil, err
}

result = append(result, children...)
}

return result, nil
}

0 comments on commit 3d463f7

Please sign in to comment.