Skip to content

Commit

Permalink
Introducing options from the CLI and unit test to confirm strict lint…
Browse files Browse the repository at this point in the history
…ing of documentation comments
  • Loading branch information
edmondop committed Jun 29, 2023
1 parent 7717a24 commit 180c98d
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ jobs:
- uses: goreleaser/goreleaser-action@v2
with:
args: release --snapshot --skip-sign
env:
PROJECT_ROOT: ./
- if: always()
run: rm -f ${HOME}/.docker/config.json
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.NORWOODJ_ORG_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
PROJECT_ROOT: ./
- if: always()
run: rm -f ${HOME}/.docker/config.json
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,76 @@ markdown or markdownX files to be processed by Gatsby or Hugo into
a static Web/Javascript page.

For a more concrete examples on how to do these custom rendering, see [example here](./example-charts/custom-value-notation-type/README.md)


## Strict linting

Sometimes you might want to enforce helm-docs to fail when some values are not documented correctly.

By default, this option is turned off:

```shell
./helm-docs -c example-charts/helm-3
INFO[2023-06-29T07:54:29-07:00] Found Chart directories [.]
INFO[2023-06-29T07:54:29-07:00] Generating README Documentation for chart example-charts/helm-3
```

but you can use the `-x` flag to turn it on:

```shell
helm-docs -x -c example-charts/helm-3
INFO[2023-06-29T07:55:12-07:00] Found Chart directories [.]
WARN[2023-06-29T07:55:12-07:00] Error parsing information for chart ., skipping: values without documentation:
controller
controller.name
controller.image
controller.extraVolumes.[0].name
controller.extraVolumes.[0].configMap
controller.extraVolumes.[0].configMap.name
controller.livenessProbe.httpGet
controller.livenessProbe.httpGet.port
controller.publishService
controller.service
controller.service.annotations
controller.service.annotations.external-dns.alpha.kubernetes.io/hostname
```

The CLI also supports excluding fields by regexp using the `-z` argument

```shell
helm-docs -x -z="controller.*" -c example-charts/helm-3
INFO[2023-06-29T08:18:55-07:00] Found Chart directories [.]
INFO[2023-06-29T08:18:55-07:00] Generating README Documentation for chart example-charts/helm-3
```

Multiple regexp can be passed, as in the following example:

```shell
helm-docs -x -z="controller.image.*" -z="controller.service.*" -z="controller.extraVolumes.*" -c example-charts/helm-3
INFO[2023-06-29T08:21:04-07:00] Found Chart directories [.]
WARN[2023-06-29T08:21:04-07:00] Error parsing information for chart ., skipping: values without documentation:
controller
controller.name
controller.livenessProbe.httpGet
controller.livenessProbe.httpGet.port
controller.publishService
```

It is also possible to ignore specific errors using the `-y` argument.

```shell
helm-docs -x -y="controller.name" -y="controller.service" -c example-charts/helm-3
INFO[2023-06-29T08:23:40-07:00] Found Chart directories [.]
WARN[2023-06-29T08:23:40-07:00] Error parsing information for chart ., skipping: values without documentation:
controller
controller.image
controller.extraVolumes.[0].name
controller.extraVolumes.[0].configMap
controller.extraVolumes.[0].configMap.name
controller.livenessProbe.httpGet
controller.livenessProbe.httpGet.port
controller.publishService
controller.service.annotations
controller.service.annotations.external-dns.alpha.kubernetes.io/hostname
```
3 changes: 3 additions & 0 deletions cmd/helm-docs/command_line.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func newHelmDocsCommand(run func(cmd *cobra.Command, args []string)) (*cobra.Com
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")
command.PersistentFlags().StringSliceP("chart-to-generate", "g", []string{}, "List of charts that will have documentation generated. Comma separated, no space. Empty list - generate for all charts in chart-search-root")
command.PersistentFlags().BoolP("documentation-strict-mode", "x", false, "Fail the generation of docs if there are undocumented values")
command.PersistentFlags().StringSliceP("documentation-strict-ignore-absent", "y", []string{"service.type", "image.repository", "image.tag"}, "A comma separate values which are allowed not to be documented in strict mode")
command.PersistentFlags().StringSliceP("documentation-strict-ignore-absent-regex", "z", []string{".*service\\.type", ".*image\\.repository", ".*image\\.tag"}, "A comma separate values which are allowed not to be documented in strict mode")

viper.AutomaticEnv()
viper.SetEnvPrefix("HELM_DOCS")
Expand Down
24 changes: 23 additions & 1 deletion cmd/helm-docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -53,6 +54,23 @@ func parallelProcessIterable(iterable interface{}, parallelism int, visitFn func
wg.Wait()
}

func getDocumentationParsingConfigFromArgs() (helm.ChartValuesDocumentationParsingConfig, error) {
var regexps []*regexp.Regexp
regexpStrings := viper.GetStringSlice("documentation-strict-ignore-absent-regex")
for _, item := range regexpStrings {
regex, err := regexp.Compile(item)
if err != nil {
return helm.ChartValuesDocumentationParsingConfig{}, err
}
regexps = append(regexps, regex)
}
return helm.ChartValuesDocumentationParsingConfig{
StrictMode: viper.GetBool("documentation-strict-mode"),
AllowedMissingValuePaths: viper.GetStringSlice("documentation-strict-ignore-absent"),
AllowedMissingValueRegexps: regexps,
}, nil
}

func readDocumentationInfoByChartPath(chartSearchRoot string, parallelism int) (map[string]helm.ChartDocumentationInfo, error) {
var fullChartSearchRoot string

Expand All @@ -79,10 +97,14 @@ func readDocumentationInfoByChartPath(chartSearchRoot string, parallelism int) (

documentationInfoByChartPath := make(map[string]helm.ChartDocumentationInfo, len(chartDirs))
documentationInfoByChartPathMu := &sync.Mutex{}
documentationParsingConfig, err := getDocumentationParsingConfigFromArgs()
if err != nil {
return nil, fmt.Errorf("error parsing the linting config%w", err)
}

parallelProcessIterable(chartDirs, parallelism, func(elem interface{}) {
chartDir := elem.(string)
info, err := helm.ParseChartInformation(filepath.Join(chartSearchRoot, chartDir))
info, err := helm.ParseChartInformation(filepath.Join(chartSearchRoot, chartDir), documentationParsingConfig)
if err != nil {
log.Warnf("Error parsing information for chart %s, skipping: %s", chartDir, err)
return
Expand Down
15 changes: 15 additions & 0 deletions example-charts/fully-documented/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v2
name: nginx-ingress
description: A simple wrapper around the stable/nginx-ingress chart that adds a few of our conventions
version: "0.2.0"
home: "https://github.com/norwoodj/helm-docs/tree/master/example-charts/nginx-ingress"
sources: ["https://github.com/norwoodj/helm-docs/tree/master/example-charts/nginx-ingress"]
engine: gotpl
type: application
maintainers:
- email: norwood.john.m@gmail.com
name: John Norwood
dependencies:
- name: nginx-ingress
version: "0.22.1"
repository: "@stable"
34 changes: 34 additions & 0 deletions example-charts/fully-documented/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# nginx-ingress

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

A simple wrapper around the stable/nginx-ingress chart that adds a few of our conventions

**Homepage:** <https://github.com/norwoodj/helm-docs/tree/master/example-charts/nginx-ingress>

## Maintainers

| Name | Email | Url |
| ---- | ------ | --- |
| John Norwood | <norwood.john.m@gmail.com> | |

## Source Code

* <https://github.com/norwoodj/helm-docs/tree/master/example-charts/nginx-ingress>

## Requirements

| Repository | Name | Version |
|------------|------|---------|
| @stable | nginx-ingress | 0.22.1 |

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| controller | object | `{"image":{"repository":"nginx-ingress-controller","tag":"18.0831"},"name":"controller"}` | The controller |
| controller.image | object | `{"repository":"nginx-ingress-controller","tag":"18.0831"}` | The image of the controller |
| controller.image.repository | string | `"nginx-ingress-controller"` | The repository of the controller |
| controller.image.tag | string | `"18.0831"` | The tag of the image of the controller |
| controller.name | string | `"controller"` | The name of the controller |

10 changes: 10 additions & 0 deletions example-charts/fully-documented/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# controller -- The controller
controller:
# controller.name -- The name of the controller
name: controller
# controller.image -- The image of the controller
image:
# controller.image.repository -- The repository of the controller
repository: nginx-ingress-controller
# controller.image.tag -- The tag of the image of the controller
tag: "18.0831"
77 changes: 68 additions & 9 deletions pkg/helm/chart_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ type ChartDocumentationInfo struct {
ChartValuesDescriptions map[string]ChartValueDescription
}

type ChartValuesDocumentationParsingConfig struct {
StrictMode bool
AllowedMissingValuePaths []string
AllowedMissingValueRegexps []*regexp.Regexp
}

func getYamlFileContents(filename string) ([]byte, error) {
if _, err := os.Stat(filename); os.IsNotExist(err) {
return nil, err
Expand Down Expand Up @@ -175,7 +181,57 @@ func parseChartValuesFile(chartDirectory string) (yaml.Node, error) {
return values, err
}

func parseChartValuesFileComments(chartDirectory string) (map[string]ChartValueDescription, error) {
func checkDocumentation(rootNode *yaml.Node, comments map[string]ChartValueDescription, config ChartValuesDocumentationParsingConfig) error {
valuesWithoutDocs := collectValuesWithoutDoc(rootNode.Content[0], comments, make([]string, 0))
valuesWithoutDocsAfterIgnore := make([]string, 0)
for _, valueWithoutDoc := range valuesWithoutDocs {
ignored := false
for _, ignorableValuePath := range config.AllowedMissingValuePaths {
ignored = ignored || valueWithoutDoc == ignorableValuePath
}
for _, ignorableValueRegexp := range config.AllowedMissingValueRegexps {
ignored = ignored || ignorableValueRegexp.MatchString(valueWithoutDoc)
}
if !ignored {
valuesWithoutDocsAfterIgnore = append(valuesWithoutDocsAfterIgnore, valueWithoutDoc)
}
}
if len(valuesWithoutDocsAfterIgnore) > 0 {
return fmt.Errorf("values without documentation: \n%s", strings.Join(valuesWithoutDocsAfterIgnore, "\n"))
}
return nil
}

func collectValuesWithoutDoc(node *yaml.Node, comments map[string]ChartValueDescription, currentPath []string) []string {
valuesWithoutDocs := make([]string, 0)
switch node.Kind {
case yaml.MappingNode:
for i := 0; i < len(node.Content); i += 2 {
keyNode, valueNode := node.Content[i], node.Content[i+1]
currentPath = append(currentPath, keyNode.Value)
pathString := strings.Join(currentPath, ".")
if _, ok := comments[pathString]; !ok {
valuesWithoutDocs = append(valuesWithoutDocs, pathString)
}

childValuesWithoutDoc := collectValuesWithoutDoc(valueNode, comments, currentPath)
valuesWithoutDocs = append(valuesWithoutDocs, childValuesWithoutDoc...)

currentPath = currentPath[:len(currentPath)-1]
}
case yaml.SequenceNode:
for i := 0; i < len(node.Content); i++ {
valueNode := node.Content[i]
currentPath = append(currentPath, fmt.Sprintf("[%d]", i))
childValuesWithoutDoc := collectValuesWithoutDoc(valueNode, comments, currentPath)
valuesWithoutDocs = append(valuesWithoutDocs, childValuesWithoutDoc...)
currentPath = currentPath[:len(currentPath)-1]
}
}
return valuesWithoutDocs
}

func parseChartValuesFileComments(chartDirectory string, values *yaml.Node, lintingConfig ChartValuesDocumentationParsingConfig) (map[string]ChartValueDescription, error) {
valuesPath := filepath.Join(chartDirectory, viper.GetString("values-file"))
valuesFile, err := os.Open(valuesPath)

Expand All @@ -189,20 +245,18 @@ func parseChartValuesFileComments(chartDirectory string) (map[string]ChartValueD
scanner := bufio.NewScanner(valuesFile)
foundValuesComment := false
commentLines := make([]string, 0)
currentLineIdx := -1

for scanner.Scan() {
currentLineIdx++
currentLine := scanner.Text()

// If we've not yet found a values comment with a key name, try and find one on each line
if !foundValuesComment {
match := valuesDescriptionRegex.FindStringSubmatch(currentLine)
if len(match) < 3 {
if len(match) < 3 || match[1] == "" {
continue
}
if match[1] == "" {
continue
}

foundValuesComment = true
commentLines = append(commentLines, currentLine)
continue
Expand All @@ -225,11 +279,16 @@ func parseChartValuesFileComments(chartDirectory string) (map[string]ChartValueD
commentLines = make([]string, 0)
foundValuesComment = false
}

if lintingConfig.StrictMode {
err := checkDocumentation(values, keyToDescriptions, lintingConfig)
if err != nil {
return nil, err
}
}
return keyToDescriptions, nil
}

func ParseChartInformation(chartDirectory string) (ChartDocumentationInfo, error) {
func ParseChartInformation(chartDirectory string, documentationParsingConfig ChartValuesDocumentationParsingConfig) (ChartDocumentationInfo, error) {
var chartDocInfo ChartDocumentationInfo
var err error

Expand All @@ -250,7 +309,7 @@ func ParseChartInformation(chartDirectory string) (ChartDocumentationInfo, error
}

chartDocInfo.ChartValues = &chartValues
chartDocInfo.ChartValuesDescriptions, err = parseChartValuesFileComments(chartDirectory)
chartDocInfo.ChartValuesDescriptions, err = parseChartValuesFileComments(chartDirectory, &chartValues, documentationParsingConfig)
if err != nil {
return chartDocInfo, err
}
Expand Down
Loading

0 comments on commit 180c98d

Please sign in to comment.