Skip to content

Commit 713946e

Browse files
committed
recursive doc generation
1 parent 4344b05 commit 713946e

File tree

7 files changed

+198
-46
lines changed

7 files changed

+198
-46
lines changed

cmd/cmd.go

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/random-dwi/helm-doc/writer"
1010
"github.com/spf13/cobra"
1111
"k8s.io/helm/pkg/chartutil"
12+
"k8s.io/helm/pkg/proto/hapi/chart"
1213
"log"
1314
"os"
1415
)
@@ -59,7 +60,7 @@ func init() {
5960

6061
func run(cmd *cobra.Command, args []string) error {
6162
if len(args) < 1 {
62-
return errors.New("chart is required")
63+
return errors.New("c is required")
6364
}
6465

6566
if flags.Verbose {
@@ -68,7 +69,7 @@ func run(cmd *cobra.Command, args []string) error {
6869

6970
output.Debugf("helm home: %s", os.Getenv("HELM_HOME"))
7071

71-
output.Debugf("Original chart version: %q", flags.Version)
72+
output.Debugf("Original c version: %q", flags.Version)
7273
if flags.Version == "" && flags.Devel {
7374
output.Debugf("setting version to >0.0.0-0")
7475
flags.Version = ">0.0.0-0"
@@ -90,37 +91,42 @@ func run(cmd *cobra.Command, args []string) error {
9091

9192
var gen writer.DocumentationWriter = writer.NewMarkdownWriter(os.Stdout)
9293

94+
parentCharts := make(map[*chart.Chart]*chart.Chart)
95+
writeChartDocs(c, 1, gen, parentCharts, nil)
96+
97+
return nil
98+
}
99+
100+
func writeChartDocs(chart *chart.Chart, layer int, gen writer.DocumentationWriter, parentCharts map[*chart.Chart]*chart.Chart, parent *chart.Chart) {
101+
93102
var dependencyNames []string
94103

95-
for _, dependency := range c.Dependencies {
104+
for _, dependency := range chart.Dependencies {
96105
dependencyNames = append(dependencyNames, dependency.Metadata.Name)
97106
}
98107

99-
output.Debugf("generating docs for %s:%s", c.Metadata.Name, c.Metadata.Version)
100-
docs, err := generator.GenerateDocs(c, dependencyNames, flags)
108+
parentCharts[chart] = parent
101109

102-
gen.WriteMetaData(c.Metadata, 1)
103-
gen.WriteDocs(docs)
110+
output.Debugf("generating docs for %s:%s", chart.Metadata.Name, chart.Metadata.Version)
111+
docs, err := generator.GenerateDocs(chart, dependencyNames, parentCharts, flags)
104112

105-
if len(c.Dependencies) > 0 {
106-
gen.WriteChapter("Dependencies", 2)
113+
if err != nil {
114+
if parent == nil || flags.VerifyDependencies {
115+
output.Failf("%v", err)
116+
} else {
117+
output.Warnf("%v", err)
118+
}
107119
}
108120

109-
for _, dependency := range c.Dependencies {
110-
output.Debugf("generating docs for %s:%s", dependency.Metadata.Name, dependency.Metadata.Version)
111-
docs, err := generator.GenerateDocs(dependency, nil, flags)
121+
gen.WriteMetaData(chart.Metadata, layer)
122+
gen.WriteDocs(docs)
112123

113-
if err != nil {
114-
if flags.VerifyDependencies {
115-
output.Failf("%v", err)
116-
} else {
117-
output.Warnf("%v", err)
118-
}
119-
} else {
120-
gen.WriteMetaData(dependency.Metadata, 3)
121-
gen.WriteDocs(docs)
124+
if len(chart.Dependencies) > 0 {
125+
layer++
126+
gen.WriteChapter("Dependencies", layer)
127+
layer++
128+
for _, dependency := range chart.Dependencies {
129+
writeChartDocs(dependency, layer, gen, parentCharts, chart)
122130
}
123131
}
124-
125-
return nil
126132
}

generator/doc_generator.go

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"github.com/ghodss/yaml"
66
"github.com/golang/protobuf/ptypes/any"
7+
"github.com/random-dwi/helm-doc/helm"
78
"github.com/random-dwi/helm-doc/output"
89
"k8s.io/helm/pkg/proto/hapi/chart"
910
"regexp"
@@ -33,54 +34,85 @@ type ConfigDoc struct {
3334
ExampleValue interface{}
3435
}
3536

36-
func GenerateDocs(chart *chart.Chart, ignoredPrefixes []string, flags CommandFlags) (map[string]*ConfigDoc, error) {
37+
func GenerateDocs(c *chart.Chart, ignoredPrefixes []string, parentCharts map[*chart.Chart]*chart.Chart, flags CommandFlags) (map[string]*ConfigDoc, error) {
3738

38-
values, err := parseYaml([]byte(chart.Values.Raw))
39+
var allValues = make(map[string]map[string]interface{})
40+
var valueSource []string
3941

40-
if err != nil {
41-
return nil, fmt.Errorf("unable to read values for %s:%s: %v", chart.Metadata.Name, chart.Metadata.Version, err)
42+
var currentChart = c
43+
var currentPrefix = ""
44+
45+
for currentChart != nil {
46+
valueSource = append(valueSource, currentChart.Metadata.Name)
47+
48+
var rawValues []byte
49+
if currentChart.Values == nil {
50+
output.Debugf("chart has no values.yaml")
51+
rawValues = []byte("")
52+
} else {
53+
rawValues = []byte(currentChart.Values.Raw)
54+
}
55+
56+
values, err := parseYaml(rawValues)
57+
58+
if err != nil {
59+
return nil, fmt.Errorf("unable to read values for %s:%s: %v", currentChart.Metadata.Name, currentChart.Metadata.Version, err)
60+
}
61+
62+
if currentPrefix != "" {
63+
val := findValueForKeyAndGlobal(currentPrefix, values, values["global"])
64+
values, _ = val.(map[string]interface{})
65+
currentPrefix = currentChart.Metadata.Name + "." + currentPrefix
66+
} else {
67+
currentPrefix = currentChart.Metadata.Name
68+
}
69+
70+
allValues[currentChart.Metadata.Name] = values
71+
currentChart = parentCharts[currentChart]
4272
}
4373

44-
definitions, err := findAndParseYaml(chart.Files, "definitions.yaml")
74+
definitions, err := findAndParseYaml(c.Files, "definitions.yaml")
4575

4676
if err != nil {
47-
return nil, fmt.Errorf("unable to read definitions for %s:%s: %v", chart.Metadata.Name, chart.Metadata.Version, err)
77+
return nil, fmt.Errorf("unable to read definitions for %s:%s: %v", c.Metadata.Name, c.Metadata.Version, err)
4878
}
4979

50-
examples, err := findAndParseYaml(chart.Files, "examples.yaml")
80+
examples, err := findAndParseYaml(c.Files, "examples.yaml")
5181

5282
if err != nil {
5383
if flags.VerifyExamples {
54-
return nil, fmt.Errorf("unable to read examples for %s:%s: %v", chart.Metadata.Name, chart.Metadata.Version, err)
84+
return nil, fmt.Errorf("unable to read examples for %s:%s: %v", c.Metadata.Name, c.Metadata.Version, err)
5585
} else {
56-
output.Warnf("unable to read examples for %s:%s: %v", chart.Metadata.Name, chart.Metadata.Version, err)
86+
output.Warnf("unable to read examples for %s:%s: %v", c.Metadata.Name, c.Metadata.Version, err)
5787
}
5888
}
5989

60-
return generate(definitions, values, examples, ignoredPrefixes, flags), nil
90+
return generate(definitions, allValues, valueSource, examples, ignoredPrefixes, flags), nil
6191
}
6292

63-
func generate(definitions map[string]interface{}, values map[string]interface{}, examples map[string]interface{}, ignoredPrefixes []string, flags CommandFlags) map[string]*ConfigDoc {
93+
func generate(definitions map[string]interface{}, allValues map[string]map[string]interface{}, valueSource []string, examples map[string]interface{}, ignoredPrefixes []string, flags CommandFlags) map[string]*ConfigDoc {
6494

6595
docs := convertToConfigDocs("", definitions)
6696

6797
if len(ignoredPrefixes) > 0 {
68-
for key := range values {
69-
if containsString(ignoredPrefixes, key) {
70-
delete(values, key)
98+
for _, source := range valueSource {
99+
for key := range allValues[source] {
100+
if containsString(ignoredPrefixes, key) {
101+
delete(allValues[source], key)
102+
}
71103
}
72104
}
73105
}
74106

75107
if flags.VerifyValues {
76-
missingKeys := validateDefaultValues("", definitions, values)
108+
missingKeys := validateDefaultValues("", definitions, allValues[valueSource[0]])
77109
if len(missingKeys) > 0 {
78110
var prefix = "\n\t"
79111
output.Failf("undocumented values detected: %s%s", prefix, strings.Join(missingKeys, prefix))
80112
}
81113
}
82114

83-
docs = insertDefaultValues(docs, values)
115+
docs = insertDefaultValues(docs, allValues, valueSource)
84116

85117
if examples != nil {
86118
docs = insertExampleValues(docs, examples, flags)
@@ -184,15 +216,34 @@ func validateDefaultValues(parentKey string, definitions map[string]interface{},
184216
return missingKeys
185217
}
186218

187-
func insertDefaultValues(docs map[string]*ConfigDoc, values map[string]interface{}) map[string]*ConfigDoc {
219+
func insertDefaultValues(docs map[string]*ConfigDoc, allValues map[string]map[string]interface{}, valueSource []string) map[string]*ConfigDoc {
188220

189221
for globalKey, configDoc := range docs {
190-
configDoc.DefaultValue = findValueForKey(globalKey, values, false)
222+
var defaultValue interface{} = nil
223+
for _, source := range valueSource {
224+
defaultValue = mergeValues(findValueForKey(globalKey, allValues[source], false), defaultValue)
225+
}
226+
configDoc.DefaultValue = defaultValue
191227
}
192228

193229
return docs
194230
}
195231

232+
func mergeValues(defaultParent interface{}, defaultChild interface{}) interface{} {
233+
parentMap, parentIsMap := defaultParent.(map[string]interface{})
234+
childMap, childIsMap := defaultChild.(map[string]interface{})
235+
if !parentIsMap || !childIsMap {
236+
// parent overwrites child
237+
if defaultParent != nil {
238+
return defaultParent
239+
} else {
240+
return defaultChild
241+
}
242+
} else {
243+
return helm.MergeValues(childMap, parentMap)
244+
}
245+
}
246+
196247
func insertExampleValues(docs map[string]*ConfigDoc, examples map[string]interface{}, flags CommandFlags) map[string]*ConfigDoc {
197248

198249
var missingExamples []string
@@ -206,7 +257,7 @@ func insertExampleValues(docs map[string]*ConfigDoc, examples map[string]interfa
206257

207258
if missingExamples != nil {
208259
var prefix = "\n\t"
209-
output.Failf("when --force-examples is true an example needs to be provided for every config without default: %s%s", prefix, strings.Join(missingExamples, prefix))
260+
output.Failf("when --verify-examples is true an example needs to be provided for every config without default: %s%s", prefix, strings.Join(missingExamples, prefix))
210261
}
211262

212263
return docs
@@ -257,6 +308,36 @@ func findDefinitionForKeyOrParentKey(globalKey string, definitions map[string]in
257308
return false
258309
}
259310

311+
func findValueForKeyAndGlobal(globalKey string, values map[string]interface{}, global interface{}) interface{} {
312+
313+
keys := strings.Split(globalKey, ".")
314+
315+
for i := range keys {
316+
leftMostKeys := keys[:len(keys)-i]
317+
joinedKey := strings.Join(leftMostKeys, ".")
318+
319+
if subValues, exists := values[joinedKey]; exists {
320+
subKey := strings.TrimPrefix(strings.TrimPrefix(globalKey, joinedKey), ".")
321+
322+
newMap, isMap := subValues.(map[string]interface{})
323+
if isMap {
324+
if subKey == "" {
325+
newMap["global"] = global
326+
return newMap
327+
} else {
328+
return findValueForKeyAndGlobal(subKey, newMap, global)
329+
}
330+
} else {
331+
globalMap := make(map[string]interface{})
332+
globalMap["global"] = global
333+
return globalMap
334+
}
335+
}
336+
}
337+
338+
return nil
339+
}
340+
260341
// find value for a given key or nil if it does not exist
261342
// if `useParentValue` is true, instead of nil the parent value is returned if available
262343
func findValueForKey(globalKey string, values map[string]interface{}, useParentValue bool) interface{} {

generator/doc_generator_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,31 @@ func Test_findValueForKey(t *testing.T) {
7070
}
7171
}
7272

73+
func Test_mergeValues(t *testing.T) {
74+
type args struct {
75+
defaultParent interface{}
76+
defaultChild interface{}
77+
}
78+
tests := []struct {
79+
name string
80+
args args
81+
want interface{}
82+
}{
83+
{name: "test_parent_nil", args: args{defaultParent: nil, defaultChild: parseJson(`{"global": "child"}`)}, want: parseJson(`{"global": "child"}`)},
84+
{name: "test_string_merge", args: args{defaultParent: parseJson(`{"global": "parent"}`), defaultChild: parseJson(`{"global": "child"}`)}, want: parseJson(`{"global": "parent"}`)},
85+
{name: "test_string_overwrite", args: args{defaultParent: `"global"`, defaultChild: parseJson(`{"global": "child"}`)}, want: `"global"`},
86+
{name: "test_map_overwrite", args: args{defaultParent: parseJson(`{"global": "parent"}`), defaultChild: `"somestring"`}, want: parseJson(`{"global": "parent"}`)},
87+
{name: "test_map_merge", args: args{defaultParent: parseJson(`{"global": {"hello": "parent"}}`), defaultChild: parseJson(`{"global": {"hello": "child", "other": "value"}}`)}, want: parseJson(`{"global": {"hello": "parent", "other": "value"}}`)},
88+
}
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
if got := mergeValues(tt.args.defaultParent, tt.args.defaultChild); !reflect.DeepEqual(got, tt.want) {
92+
t.Errorf("mergeValues() = %v, want %v", got, tt.want)
93+
}
94+
})
95+
}
96+
}
97+
7398
func parseJson(value string) map[string]interface{} {
7499
valueMap := map[string]interface{}{}
75100

helm/helm.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,35 @@ func LocateChartPath(repoURL, username, password, name, version string, verify b
9797

9898
return filename, fmt.Errorf("failed to download %q (hint: running `helm repo update` may help)", name)
9999
}
100+
101+
// Copied from Helm.
102+
func MergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
103+
for k, v := range src {
104+
// If the key doesn't exist already, then just set the key to that value
105+
if _, exists := dest[k]; !exists {
106+
dest[k] = v
107+
continue
108+
}
109+
nextMap, ok := v.(map[string]interface{})
110+
// If it isn't another map, overwrite the value
111+
if !ok {
112+
dest[k] = v
113+
continue
114+
}
115+
// If the key doesn't exist already, then just set the key to that value
116+
if _, exists := dest[k]; !exists {
117+
dest[k] = nextMap
118+
continue
119+
}
120+
// Edge case: If the key exists in the destination, but isn't a map
121+
destMap, isMap := dest[k].(map[string]interface{})
122+
// If the source map has a map for this key, prefer it
123+
if !isMap {
124+
dest[k] = v
125+
continue
126+
}
127+
// If we got to this point, it is a map in both, so merge them
128+
dest[k] = MergeValues(destMap, nextMap)
129+
}
130+
return dest
131+
}

install_plugin_dev.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
helm plugin remove doc
3+
rm `helm home`/plugins/helm-doc
4+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5+
ln -s $DIR `helm home`/plugins/helm-doc
6+
echo "created symlink to `helm home`/plugins/helm-doc"

link.sh

Lines changed: 0 additions & 3 deletions
This file was deleted.

writer/markdown_writer.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@ func (g MarkdownWriter) WriteMetaData(metaData *chart.Metadata, layer int) {
3636
}
3737

3838
g.fprintf(" %s\n\n", metaData.Name)
39-
g.fprintf("- **Version:** %s\n\n", metaData.Version)
39+
g.fprintf("- **Version:** %s\n", metaData.Version)
4040
g.fprintf("- **Description:** %s\n", metaData.Description)
4141
g.fprintf("\n")
4242
}
4343

4444
func (g MarkdownWriter) WriteDocs(docs map[string]*generator.ConfigDoc) {
4545

46+
if len(docs) == 0 {
47+
return
48+
}
49+
4650
g.fprintf("|%s|%s|%s|%s|\n", "KEY", "DESCRIPTION", "DEFAULT", "EXAMPLE")
4751
g.fprintf("|---|---|---|---|\n")
4852

@@ -58,6 +62,7 @@ func (g MarkdownWriter) WriteDocs(docs map[string]*generator.ConfigDoc) {
5862
row := []string{"`" + key + "`", sanitize(configDoc.Description), toMarkdown(configDoc.DefaultValue), toMarkdown(configDoc.ExampleValue)}
5963
g.fprintf("|%s|\n", strings.Join(row, "|"))
6064
}
65+
g.fprintf("\n")
6166
}
6267

6368
func toMarkdown(object interface{}) string {

0 commit comments

Comments
 (0)