diff --git a/pkg/document/values.go b/pkg/document/values.go index 05c7436e..d4e7eba9 100644 --- a/pkg/document/values.go +++ b/pkg/document/values.go @@ -234,7 +234,7 @@ func createValueRowsFromList( // We have a nonempty list with a description, document it, and mark that leaf nodes underneath it should not be // documented without descriptions - if hasDescription || (autoDescription.Description != "" && autoDescription.NotationType == "") { + if (hasDescription || autoDescription.Description != "") && autoDescription.NotationType == "" { jsonableObject := convertHelmValuesToJsonable(values) listRow, err := createValueRow(prefix, jsonableObject, description, autoDescription, key.Column, key.Line) @@ -244,7 +244,7 @@ func createValueRowsFromList( valueRows = append(valueRows, listRow) documentLeafNodes = false - } else if hasDescription || (autoDescription.Description != "" && autoDescription.NotationType != "") { + } else if hasDescription || autoDescription.Description != "" { // If it has NotationType described, then use that var notationValue interface{} var err error @@ -323,7 +323,7 @@ func createValueRowsFromObject( // We have a nonempty object with a description, document it, and mark that leaf nodes underneath it should not be // documented without descriptions - if hasDescription || (autoDescription.Description != "" && autoDescription.NotationType == "") { + if (hasDescription || autoDescription.Description != "") && autoDescription.NotationType == "" { jsonableObject := convertHelmValuesToJsonable(values) objectRow, err := createValueRow(nextPrefix, jsonableObject, description, autoDescription, key.Column, key.Line) @@ -333,7 +333,7 @@ func createValueRowsFromObject( valueRows = append(valueRows, objectRow) documentLeafNodes = false - } else if hasDescription || (autoDescription.Description != "" && autoDescription.NotationType != "") { + } else if hasDescription || autoDescription.Description != "" { // If it has NotationType described, then use that var notationValue interface{} diff --git a/pkg/document/values_test.go b/pkg/document/values_test.go index d08a639c..8c55c50b 100644 --- a/pkg/document/values_test.go +++ b/pkg/document/values_test.go @@ -1531,6 +1531,31 @@ animals: assert.Equal(t, yamlType, valuesRows[4].NotationType) } +func TestExtractValueNotationTypeWithDescription(t *testing.T) { + helmValues := parseYamlValues(` + # -- The deployment label + # @notationType -- yaml +labels: + client-name: my-boss +# -- Access modes +# @notationType -- yaml +accessModes: + - ReadWriteOnce +`) + + descriptions := map[string]helm.ChartValueDescription{ + "labels": {Description: "External description"}, + "accessModes": {Description: "External description"}, + } + + valuesRows, err := getSortedValuesTableRows(helmValues, descriptions) + + assert.Nil(t, err) + assert.Len(t, valuesRows, 2) + assert.Equal(t, "- ReadWriteOnce\n", valuesRows[0].Default) + assert.Equal(t, "client-name: my-boss\n", valuesRows[1].Default) +} + func TestExtractCustomDeclaredType(t *testing.T) { helmValues := parseYamlValues(` animals: diff --git a/pkg/helm/chart_info.go b/pkg/helm/chart_info.go index 424afb4f..d5eacaf0 100644 --- a/pkg/helm/chart_info.go +++ b/pkg/helm/chart_info.go @@ -7,7 +7,9 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" + "strconv" "strings" log "github.com/sirupsen/logrus" @@ -251,15 +253,23 @@ func parseChartValuesFileComments(chartDirectory string, values *yaml.Node, lint foundValuesComment := false commentLines := make([]string, 0) currentLineIdx := -1 + currentValueKeySegments := make([]string, 0) 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 currentLine == "" { + continue + } + + // For value comments without keys we need to track previous keys to exactly know where we are at. + currentValueKeySegments = updateCurrentValueKeySegments(currentLine, currentValueKeySegments) + + // If we've not yet found a values comment, try and find one on each line if !foundValuesComment { match := valuesDescriptionRegex.FindStringSubmatch(currentLine) - if len(match) < 3 || match[1] == "" { + if len(match) < 3 { continue } foundValuesComment = true @@ -285,10 +295,12 @@ func parseChartValuesFileComments(chartDirectory string, values *yaml.Node, lint // If we haven't continued by this point, we didn't match any of the comment formats we want, so we need to add // the in progress value to the map, and reset to looking for a new key - key, description := ParseComment(commentLines) - if key != "" { - keyToDescriptions[key] = description + key := strings.Join(currentValueKeySegments, ".") + if strings.HasPrefix(strings.Trim(commentLines[0], " "), "# -- ") { + commentLines[0] = strings.Replace(commentLines[0], "# -- ", "# "+key+" -- ", 1) } + key, description := ParseComment(commentLines) + keyToDescriptions[key] = description commentLines = make([]string, 0) foundValuesComment = false @@ -302,6 +314,49 @@ func parseChartValuesFileComments(chartDirectory string, values *yaml.Node, lint return keyToDescriptions, nil } +func updateCurrentValueKeySegments(currentLine string, currentValueKeySegments []string) []string { + valueKeyRegex := regexp.MustCompile("^(\\s*)(-?)\\s*([^:]+):?\\s*.*$") + valueKeyMatch := valueKeyRegex.FindStringSubmatch(currentLine) + + if len(valueKeyMatch) == 4 { + // line is value key or group. + indentation := len(valueKeyMatch[1]) + valueKey := valueKeyMatch[3] + if strings.HasPrefix(valueKey, "#") { + return currentValueKeySegments + } + isArrayElement := valueKeyMatch[2] != "" + + // if current indentation is less than elements in list, we need to remove some elements. + if !isArrayElement && indentation/2 < len(currentValueKeySegments) { + currentValueKeySegments = slices.Delete(currentValueKeySegments, indentation/2, len(currentValueKeySegments)) + } + + // For yaml arrays we need the current index in the key + if isArrayElement { + previousValueKeySegment := currentValueKeySegments[len(currentValueKeySegments)-1] + currentValueKeySegments = slices.Delete(currentValueKeySegments, len(currentValueKeySegments)-1, len(currentValueKeySegments)) + keyRegex := regexp.MustCompile("^(\\w+)\\[?(\\d+)?\\]?$") + keyMatches := keyRegex.FindStringSubmatch(previousValueKeySegment) + index := 0 + if keyMatches[2] != "" { + valueInt, _ := strconv.Atoi(keyMatches[2]) + index = valueInt + 1 + } + valueKey = keyMatches[1] + "[" + strconv.Itoa(index) + "]" + } + + // We need to quote the key if value key contains special characters + if strings.ContainsAny(valueKey, "-./") { + valueKey = "\"" + valueKey + "\"" + } + + currentValueKeySegments = append(currentValueKeySegments, valueKey) + } + + return currentValueKeySegments +} + func ParseChartInformation(chartDirectory string, documentationParsingConfig ChartValuesDocumentationParsingConfig) (ChartDocumentationInfo, error) { var chartDocInfo ChartDocumentationInfo var err error diff --git a/pkg/helm/chart_info_test.go b/pkg/helm/chart_info_test.go index 3d9197b6..4f0586de 100644 --- a/pkg/helm/chart_info_test.go +++ b/pkg/helm/chart_info_test.go @@ -1,12 +1,13 @@ package helm_test import ( - "github.com/norwoodj/helm-docs/pkg/helm" - "github.com/spf13/viper" - "github.com/stretchr/testify/suite" "path/filepath" "regexp" "testing" + + "github.com/norwoodj/helm-docs/pkg/helm" + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" ) type ChartParsingTestSuite struct { @@ -94,3 +95,11 @@ func (suite *ChartParsingTestSuite) TestFullyDocumentedChartStrictModeOn() { }) suite.NoError(err) } + +func (suite *ChartParsingTestSuite) TestFullyDocumentedChartNewStyleStrictModeOn() { + chartPath := filepath.Join("test-fixtures", "fully-documented-new-style") + _, err := helm.ParseChartInformation(chartPath, helm.ChartValuesDocumentationParsingConfig{ + StrictMode: true, + }) + suite.NoError(err) +} diff --git a/pkg/helm/comment_test.go b/pkg/helm/comment_test.go new file mode 100644 index 00000000..30f52554 --- /dev/null +++ b/pkg/helm/comment_test.go @@ -0,0 +1,28 @@ +package helm_test + +import ( + "testing" + + "github.com/norwoodj/helm-docs/pkg/helm" + "github.com/stretchr/testify/assert" +) + +func TestParseComment(t *testing.T) { + commentLines := []string{ + "# controller.image.repository -- The repository of the controller image", + } + valueKey, c := helm.ParseComment(commentLines) + + assert.Equal(t, "controller.image.repository", valueKey) + assert.Equal(t, "The repository of the controller image", c.Description) +} + +func TestParseCommentNewStyle(t *testing.T) { + commentLines := []string{ + "# -- The repository of the controller image", + } + valueKey, c := helm.ParseComment(commentLines) + + assert.Equal(t, "", valueKey) + assert.Equal(t, "The repository of the controller image", c.Description) +} diff --git a/pkg/helm/test-fixtures/full-template/README.md b/pkg/helm/test-fixtures/full-template/README.md index 8b3d3773..5fa3228a 100644 --- a/pkg/helm/test-fixtures/full-template/README.md +++ b/pkg/helm/test-fixtures/full-template/README.md @@ -68,7 +68,7 @@ https://github.com/norwoodj/helm-docs/tree/master/example-charts/full-template ## `chart.maintainersTable` -| Name | Email | Url | +| Name | Email | URL | | ---- | ------ | --- | | John Norwood | | | @@ -76,7 +76,7 @@ https://github.com/norwoodj/helm-docs/tree/master/example-charts/full-template ## Maintainers -| Name | Email | Url | +| Name | Email | URL | | ---- | ------ | --- | | John Norwood | | | @@ -162,5 +162,3 @@ Kubernetes: `<=1.18` | controller.service.annotations."external-dns.alpha.kubernetes.io/hostname" | string | `"stupidchess.jmn23.com"` | Hostname to be assigned to the ELB for the service | | controller.service.type | string | `"LoadBalancer"` | | ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/pkg/helm/test-fixtures/fully-documented-new-style/Chart.yaml b/pkg/helm/test-fixtures/fully-documented-new-style/Chart.yaml new file mode 100644 index 00000000..fbd50ebc --- /dev/null +++ b/pkg/helm/test-fixtures/fully-documented-new-style/Chart.yaml @@ -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" diff --git a/pkg/helm/test-fixtures/fully-documented-new-style/README.md b/pkg/helm/test-fixtures/fully-documented-new-style/README.md new file mode 100644 index 00000000..69d3bcea --- /dev/null +++ b/pkg/helm/test-fixtures/fully-documented-new-style/README.md @@ -0,0 +1,39 @@ +# 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:** + +## Maintainers + +| Name | Email | URL | +| ---- | ------ | --- | +| John Norwood | | | + +## Source Code + +* + +## 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 | +| image | object | `{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"lucernae/django-sample","tag":"3.1"}` | Image map | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy | +| image.registry | string | `"docker.io"` | Image registry | +| image.repository | string | `"lucernae/django-sample"` | Image repository | +| image.tag | string | `"3.1"` | Image tag | + diff --git a/pkg/helm/test-fixtures/fully-documented-new-style/values.yaml b/pkg/helm/test-fixtures/fully-documented-new-style/values.yaml new file mode 100644 index 00000000..6e48999e --- /dev/null +++ b/pkg/helm/test-fixtures/fully-documented-new-style/values.yaml @@ -0,0 +1,21 @@ +# -- The controller +controller: + # -- The name of the controller + name: controller + # -- The image of the controller + image: + # -- The repository of the controller + repository: nginx-ingress-controller + # -- The tag of the image of the controller + tag: "18.0831" + +# -- Image map +image: + # -- Image registry + registry: docker.io + # -- Image repository + repository: lucernae/django-sample + # -- Image tag + tag: "3.1" + # -- Image pullPolicy + pullPolicy: IfNotPresent diff --git a/pkg/helm/test-fixtures/fully-documented/README.md b/pkg/helm/test-fixtures/fully-documented/README.md index 30119375..69d3bcea 100644 --- a/pkg/helm/test-fixtures/fully-documented/README.md +++ b/pkg/helm/test-fixtures/fully-documented/README.md @@ -8,7 +8,7 @@ A simple wrapper around the stable/nginx-ingress chart that adds a few of our co ## Maintainers -| Name | Email | Url | +| Name | Email | URL | | ---- | ------ | --- | | John Norwood | | | @@ -31,4 +31,9 @@ A simple wrapper around the stable/nginx-ingress chart that adds a few of our co | 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 | +| image | object | `{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"lucernae/django-sample","tag":"3.1"}` | Image map | +| image.pullPolicy | string | `"IfNotPresent"` | Image pullPolicy | +| image.registry | string | `"docker.io"` | Image registry | +| image.repository | string | `"lucernae/django-sample"` | Image repository | +| image.tag | string | `"3.1"` | Image tag | diff --git a/pkg/helm/test-fixtures/fully-documented/values.yaml b/pkg/helm/test-fixtures/fully-documented/values.yaml index d57a32d6..be415a32 100644 --- a/pkg/helm/test-fixtures/fully-documented/values.yaml +++ b/pkg/helm/test-fixtures/fully-documented/values.yaml @@ -8,3 +8,14 @@ controller: repository: nginx-ingress-controller # controller.image.tag -- The tag of the image of the controller tag: "18.0831" + +# image -- Image map +image: + # image.registry -- Image registry + registry: docker.io + # image.repository -- Image repository + repository: lucernae/django-sample + # image.tag -- Image tag + tag: "3.1" + # image.pullPolicy -- Image pullPolicy + pullPolicy: IfNotPresent