Skip to content

Commit 09c67fd

Browse files
✨ Move doc generation code away from cmd into lrcore, alongside tests. the LR ast now has a GenerateDocs function.
Signed-off-by: Preslav <preslav@mondoo.com>
1 parent 7cd2443 commit 09c67fd

File tree

4 files changed

+134
-142
lines changed

4 files changed

+134
-142
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/lr
2+
mqlr
23
dist
34
alpine-container.tar
45
centos-container.tar

providers-sdk/v1/mqlr/cmd/docs.go

Lines changed: 18 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os"
1010
"path"
1111
"path/filepath"
12-
"sort"
1312
"strings"
1413
"text/template"
1514

@@ -37,7 +36,7 @@ var docsCmd = &cobra.Command{
3736

3837
var docsYamlCmd = &cobra.Command{
3938
Use: "yaml",
40-
Short: "generates yaml docs skeleton file and merges it into existing definition",
39+
Short: "generates yaml docs skeleton file",
4140
Long: `parse an LR file and generates a yaml file structure for additional documentation.`,
4241
Args: cobra.MinimumNArgs(1),
4342
Run: func(cmd *cobra.Command, args []string) {
@@ -52,52 +51,19 @@ var docsYamlCmd = &cobra.Command{
5251
log.Error().Msg(err.Error())
5352
return
5453
}
55-
56-
// to ensure we generate the same markdown, we sort the resources first
57-
sort.SliceStable(res.Resources, func(i, j int) bool {
58-
return res.Resources[i].ID < res.Resources[j].ID
59-
})
60-
61-
filepath, err := cmd.Flags().GetString("docs-file")
62-
if err != nil {
63-
log.Fatal().Err(err).Msg("invalid argument for `file`")
64-
}
65-
6654
version, err := cmd.Flags().GetString("version")
6755
if err != nil {
6856
log.Fatal().Err(err).Msg("invalid argument for `version`")
6957
}
7058

71-
d := lrcore.LrDocs{
72-
Resources: map[string]*lrcore.LrDocsEntry{},
73-
}
74-
75-
fields := map[string][]*lrcore.BasicField{}
76-
isPrivate := map[string]bool{}
77-
for i := range res.Resources {
78-
id := res.Resources[i].ID
79-
isPrivate[id] = res.Resources[i].IsPrivate
80-
d.Resources[id] = nil
81-
if res.Resources[i].Body != nil {
82-
basicFields := []*lrcore.BasicField{}
83-
for _, f := range res.Resources[i].Body.Fields {
84-
if f.BasicField != nil {
85-
basicFields = append(basicFields, f.BasicField)
86-
}
87-
}
88-
fields[id] = basicFields
89-
}
59+
filepath, err := cmd.Flags().GetString("docs-file")
60+
if err != nil {
61+
log.Fatal().Err(err).Msg("invalid argument for `docs-file`")
9062
}
9163

92-
// default behaviour is to output the result on cli
93-
if filepath == "" {
94-
data, err := yaml.Marshal(d)
95-
if err != nil {
96-
log.Fatal().Err(err).Msg("could not marshal docs")
97-
}
98-
99-
fmt.Println(string(data))
100-
return
64+
docs, err := res.GenerateDocs(version, defaultVersionField)
65+
if err != nil {
66+
log.Fatal().Err(err).Msg("could not generate docs")
10167
}
10268

10369
// if an file was provided, we check if the file exist and merge existing content with the new resources
@@ -118,24 +84,26 @@ var docsYamlCmd = &cobra.Command{
11884
log.Info().Msg("merge content")
11985
for k := range existingData.Resources {
12086
v := existingData.Resources[k]
121-
d.Resources[k] = v
87+
docs.Resources[k] = v
12288
}
12389
}
12490

125-
// ensure default values and fields are set
126-
for k := range d.Resources {
127-
d.Resources[k] = ensureDefaults(k, d.Resources[k], version)
128-
mergeFields(version, d.Resources[k], fields[k])
129-
// Merge in other doc fields from core.lr
130-
d.Resources[k].IsPrivate = isPrivate[k]
91+
// default behaviour is to output the result on cli
92+
if filepath == "" {
93+
data, err := yaml.Marshal(docs)
94+
if err != nil {
95+
log.Fatal().Err(err).Msg("could not marshal docs")
96+
}
97+
98+
fmt.Println(string(data))
99+
return
131100
}
132101

133102
// generate content
134-
data, err := yaml.Marshal(d)
103+
data, err := yaml.Marshal(docs)
135104
if err != nil {
136105
log.Fatal().Err(err).Msg("could not marshal docs")
137106
}
138-
139107
// add license header
140108
var headerTpl *template.Template
141109
if headerFile, err := cmd.Flags().GetString("license-header-file"); err == nil && headerFile != "" {
@@ -163,89 +131,6 @@ var docsYamlCmd = &cobra.Command{
163131
},
164132
}
165133

166-
// required to be before more detail platform to ensure the right mapping
167-
var platformMappingKeys = []string{
168-
"aws", "gcp", "k8s", "azure", "azurerm", "arista", "equinix", "ms365", "msgraph", "vsphere", "esxi", "terraform", "terraform.state", "terraform.plan",
169-
}
170-
171-
var platformMapping = map[string][]string{
172-
"aws": {"aws"},
173-
"gcp": {"gcp"},
174-
"k8s": {"kubernetes"},
175-
"azure": {"azure"},
176-
"azurerm": {"azure"},
177-
"arista": {"arista-eos"},
178-
"equinix": {"equinix"},
179-
"ms365": {"microsoft365"},
180-
"msgraph": {"microsoft365"},
181-
"vsphere": {"vmware-esxi", "vmware-vsphere"},
182-
"esxi": {"vmware-esxi", "vmware-vsphere"},
183-
"terraform": {"terraform-hcl"},
184-
"terraform.state": {"terraform-state"},
185-
"terraform.plan": {"terraform-plan"},
186-
}
187-
188-
func ensureDefaults(id string, entry *lrcore.LrDocsEntry, version string) *lrcore.LrDocsEntry {
189-
for _, k := range platformMappingKeys {
190-
if entry == nil {
191-
entry = &lrcore.LrDocsEntry{}
192-
}
193-
if entry.MinMondooVersion == "" {
194-
entry.MinMondooVersion = version
195-
} else if entry.MinMondooVersion == defaultVersionField && version != defaultVersionField {
196-
// Update to specified version if previously set to default
197-
entry.MinMondooVersion = version
198-
}
199-
if strings.HasPrefix(id, k) {
200-
entry.Platform = &lrcore.LrDocsPlatform{
201-
Name: platformMapping[k],
202-
}
203-
}
204-
}
205-
return entry
206-
}
207-
208-
func mergeFields(version string, entry *lrcore.LrDocsEntry, fields []*lrcore.BasicField) {
209-
if entry == nil && len(fields) > 0 {
210-
entry = &lrcore.LrDocsEntry{}
211-
entry.Fields = map[string]*lrcore.LrDocsField{}
212-
} else if entry == nil {
213-
return
214-
} else if entry.Fields == nil {
215-
entry.Fields = map[string]*lrcore.LrDocsField{}
216-
}
217-
docFields := entry.Fields
218-
for _, f := range fields {
219-
if docFields[f.ID] == nil {
220-
fDoc := &lrcore.LrDocsField{
221-
MinMondooVersion: version,
222-
}
223-
entry.Fields[f.ID] = fDoc
224-
} else if entry.Fields[f.ID].MinMondooVersion == defaultVersionField && version != defaultVersionField {
225-
entry.Fields[f.ID].MinMondooVersion = version
226-
}
227-
// Scrub field version if same as resource
228-
if entry.Fields[f.ID].MinMondooVersion == entry.MinMondooVersion {
229-
entry.Fields[f.ID].MinMondooVersion = ""
230-
}
231-
}
232-
}
233-
234-
func extractComments(raw []string) (string, string) {
235-
if len(raw) == 0 {
236-
return "", ""
237-
}
238-
239-
for i := range raw {
240-
raw[i] = strings.Trim(raw[i][2:], " \t\n")
241-
}
242-
243-
title, rest := raw[0], raw[1:]
244-
desc := strings.Join(rest, " ")
245-
246-
return title, desc
247-
}
248-
249134
var docsJSONCmd = &cobra.Command{
250135
Use: "json",
251136
Short: "convert yaml docs manifest into json",

providers-sdk/v1/mqlr/lrcore/docs.go

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package lrcore
55

66
import (
77
"fmt"
8+
"sort"
89
"strconv"
910
"strings"
1011

@@ -23,7 +24,7 @@ type LrDocsEntry struct {
2324
Platform *LrDocsPlatform `json:"platform,omitempty"`
2425
Docs *LrDocsDocumentation `json:"docs,omitempty"`
2526
Resources []LrDocsRefs `json:"resources,omitempty"`
26-
Fields map[string]*LrDocsField `json:"fields,omitEmpty"`
27+
Fields map[string]*LrDocsField `json:"fields,omitempty"`
2728
Refs []LrDocsRefs `json:"refs,omitempty"`
2829
Snippets []LrDocsSnippet `json:"snippets,omitempty"`
2930
IsPrivate bool `json:"is_private,omitempty"`
@@ -96,3 +97,109 @@ func InjectMetadata(schema *resources.Schema, docs *LrDocs) {
9697
}
9798
}
9899
}
100+
101+
func (lr *LR) GenerateDocs(currentVersion, defaultVersion string) (LrDocs, error) {
102+
// to ensure we generate the same markdown, we sort the resources first
103+
sort.SliceStable(lr.Resources, func(i, j int) bool {
104+
return lr.Resources[i].ID < lr.Resources[j].ID
105+
})
106+
107+
docs := LrDocs{
108+
Resources: map[string]*LrDocsEntry{},
109+
}
110+
111+
fields := map[string][]*BasicField{}
112+
isPrivate := map[string]bool{}
113+
for i := range lr.Resources {
114+
id := lr.Resources[i].ID
115+
isPrivate[id] = lr.Resources[i].IsPrivate
116+
docs.Resources[id] = nil
117+
if lr.Resources[i].Body != nil {
118+
basicFields := []*BasicField{}
119+
for _, f := range lr.Resources[i].Body.Fields {
120+
if f.BasicField != nil {
121+
basicFields = append(basicFields, f.BasicField)
122+
}
123+
}
124+
fields[id] = basicFields
125+
}
126+
}
127+
128+
// ensure default values and fields are set
129+
for k := range docs.Resources {
130+
docs.Resources[k] = ensureDefaults(k, docs.Resources[k], currentVersion, defaultVersion)
131+
mergeFields(docs.Resources[k], fields[k], currentVersion, defaultVersion)
132+
// Merge in other doc fields from core.lr
133+
docs.Resources[k].IsPrivate = isPrivate[k]
134+
}
135+
136+
return docs, nil
137+
}
138+
139+
func mergeFields(entry *LrDocsEntry, fields []*BasicField, currentVersion, defaultVersion string) {
140+
if entry == nil && len(fields) > 0 {
141+
entry = &LrDocsEntry{}
142+
entry.Fields = map[string]*LrDocsField{}
143+
} else if entry == nil {
144+
return
145+
} else if entry.Fields == nil {
146+
entry.Fields = map[string]*LrDocsField{}
147+
}
148+
docFields := entry.Fields
149+
for _, f := range fields {
150+
if docFields[f.ID] == nil {
151+
fDoc := &LrDocsField{
152+
MinMondooVersion: currentVersion,
153+
}
154+
entry.Fields[f.ID] = fDoc
155+
} else if entry.Fields[f.ID].MinMondooVersion == defaultVersion && currentVersion != defaultVersion {
156+
entry.Fields[f.ID].MinMondooVersion = currentVersion
157+
}
158+
// Scrub field version if same as resource
159+
if entry.Fields[f.ID].MinMondooVersion == entry.MinMondooVersion {
160+
entry.Fields[f.ID].MinMondooVersion = ""
161+
}
162+
}
163+
}
164+
165+
func ensureDefaults(id string, entry *LrDocsEntry, currentVersion, defaultVersion string) *LrDocsEntry {
166+
for _, k := range platformMappingKeys {
167+
if entry == nil {
168+
entry = &LrDocsEntry{}
169+
}
170+
if entry.MinMondooVersion == "" {
171+
entry.MinMondooVersion = currentVersion
172+
} else if entry.MinMondooVersion == defaultVersion && currentVersion != defaultVersion {
173+
// Update to specified version if previously set to default
174+
entry.MinMondooVersion = currentVersion
175+
}
176+
if strings.HasPrefix(id, k) {
177+
entry.Platform = &LrDocsPlatform{
178+
Name: platformMapping[k],
179+
}
180+
}
181+
}
182+
return entry
183+
}
184+
185+
// required to be before more detail platform to ensure the right mapping
186+
var platformMappingKeys = []string{
187+
"aws", "gcp", "k8s", "azure", "azurerm", "arista", "equinix", "ms365", "msgraph", "vsphere", "esxi", "terraform", "terraform.state", "terraform.plan",
188+
}
189+
190+
var platformMapping = map[string][]string{
191+
"aws": {"aws"},
192+
"gcp": {"gcp"},
193+
"k8s": {"kubernetes"},
194+
"azure": {"azure"},
195+
"azurerm": {"azure"},
196+
"arista": {"arista-eos"},
197+
"equinix": {"equinix"},
198+
"ms365": {"microsoft365"},
199+
"msgraph": {"microsoft365"},
200+
"vsphere": {"vmware-esxi", "vmware-vsphere"},
201+
"esxi": {"vmware-esxi", "vmware-vsphere"},
202+
"terraform": {"terraform-hcl"},
203+
"terraform.state": {"terraform-state"},
204+
"terraform.plan": {"terraform-plan"},
205+
}
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
// Copyright (c) Mondoo, Inc.
22
// SPDX-License-Identifier: BUSL-1.1
33

4-
package cmd
4+
package lrcore
55

66
import (
77
"testing"
88

99
"github.com/stretchr/testify/assert"
10-
"go.mondoo.com/cnquery/v12/providers-sdk/v1/mqlr/lrcore"
1110
)
1211

13-
var defaultLrDocsEntry = &lrcore.LrDocsEntry{
14-
Fields: map[string]*lrcore.LrDocsField{},
12+
var defaultLrDocsEntry = &LrDocsEntry{
13+
Fields: map[string]*LrDocsField{},
1514
MinMondooVersion: "9.1.0",
1615
}
1716

1817
func TestPlatformMapping(t *testing.T) {
19-
res := ensureDefaults("terraform.plan.configuration", defaultLrDocsEntry, "9.1.0")
18+
res := ensureDefaults("terraform.plan.configuration", defaultLrDocsEntry, "9.1.0", "9.1.0")
2019
assert.Equal(t, "terraform-plan", res.Platform.Name[0])
2120

22-
res = ensureDefaults("terraform.plan.proposedChange", defaultLrDocsEntry, "9.1.0")
21+
res = ensureDefaults("terraform.plan.proposedChange", defaultLrDocsEntry, "9.1.0", "9.1.0")
2322
assert.Equal(t, "terraform-plan", res.Platform.Name[0])
2423

25-
res = ensureDefaults("terraform.state.module", defaultLrDocsEntry, "9.1.0")
24+
res = ensureDefaults("terraform.state.module", defaultLrDocsEntry, "9.1.0", "9.1.0")
2625
assert.Equal(t, "terraform-state", res.Platform.Name[0])
2726

28-
res = ensureDefaults("terraform.block", defaultLrDocsEntry, "9.1.0")
27+
res = ensureDefaults("terraform.block", defaultLrDocsEntry, "9.1.0", "9.1.0")
2928
assert.Equal(t, "terraform-hcl", res.Platform.Name[0])
3029
}

0 commit comments

Comments
 (0)