Skip to content

Commit 4c09562

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 4c09562

File tree

7 files changed

+235
-170
lines changed

7 files changed

+235
-170
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: 17 additions & 139 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,14 @@ 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-
}
90-
}
91-
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
59+
filepath, err := cmd.Flags().GetString("docs-file")
60+
if err != nil {
61+
log.Fatal().Err(err).Msg("invalid argument for `docs-file`")
10162
}
10263

10364
// if an file was provided, we check if the file exist and merge existing content with the new resources
@@ -114,28 +75,28 @@ var docsYamlCmd = &cobra.Command{
11475
if err != nil {
11576
log.Fatal().Err(err).Msg("could not load yaml data")
11677
}
78+
}
11779

118-
log.Info().Msg("merge content")
119-
for k := range existingData.Resources {
120-
v := existingData.Resources[k]
121-
d.Resources[k] = v
122-
}
80+
docs, err := res.GenerateDocs(version, defaultVersionField, existingData)
81+
if err != nil {
82+
log.Fatal().Err(err).Msg("could not generate docs")
12383
}
84+
// default behaviour is to output the result on cli
85+
if filepath == "" {
86+
data, err := yaml.Marshal(docs)
87+
if err != nil {
88+
log.Fatal().Err(err).Msg("could not marshal docs")
89+
}
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+
fmt.Println(string(data))
92+
return
13193
}
13294

13395
// generate content
134-
data, err := yaml.Marshal(d)
96+
data, err := yaml.Marshal(docs)
13597
if err != nil {
13698
log.Fatal().Err(err).Msg("could not marshal docs")
13799
}
138-
139100
// add license header
140101
var headerTpl *template.Template
141102
if headerFile, err := cmd.Flags().GetString("license-header-file"); err == nil && headerFile != "" {
@@ -163,89 +124,6 @@ var docsYamlCmd = &cobra.Command{
163124
},
164125
}
165126

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-
249127
var docsJSONCmd = &cobra.Command{
250128
Use: "json",
251129
Short: "convert yaml docs manifest into json",

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

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

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

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

66
import (
77
"fmt"
8+
"maps"
9+
"sort"
810
"strconv"
911
"strings"
1012

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

0 commit comments

Comments
 (0)