Skip to content

Commit 5e69bc7

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 ee64f7b commit 5e69bc7

File tree

8 files changed

+240
-159
lines changed

8 files changed

+240
-159
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: 13 additions & 119 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

@@ -59,74 +58,6 @@ var docsYamlCmd = &cobra.Command{
5958
},
6059
}
6160

62-
// required to be before more detail platform to ensure the right mapping
63-
var platformMappingKeys = []string{
64-
"aws", "gcp", "k8s", "azure", "azurerm", "arista", "equinix", "ms365", "msgraph", "vsphere", "esxi", "terraform", "terraform.state", "terraform.plan",
65-
}
66-
67-
var platformMapping = map[string][]string{
68-
"aws": {"aws"},
69-
"gcp": {"gcp"},
70-
"k8s": {"kubernetes"},
71-
"azure": {"azure"},
72-
"azurerm": {"azure"},
73-
"arista": {"arista-eos"},
74-
"equinix": {"equinix"},
75-
"ms365": {"microsoft365"},
76-
"msgraph": {"microsoft365"},
77-
"vsphere": {"vmware-esxi", "vmware-vsphere"},
78-
"esxi": {"vmware-esxi", "vmware-vsphere"},
79-
"terraform": {"terraform-hcl"},
80-
"terraform.state": {"terraform-state"},
81-
"terraform.plan": {"terraform-plan"},
82-
}
83-
84-
func ensureDefaults(id string, entry *lrcore.LrDocsEntry, version string) *lrcore.LrDocsEntry {
85-
for _, k := range platformMappingKeys {
86-
if entry == nil {
87-
entry = &lrcore.LrDocsEntry{}
88-
}
89-
if entry.MinMondooVersion == "" {
90-
entry.MinMondooVersion = version
91-
} else if entry.MinMondooVersion == defaultVersionField && version != defaultVersionField {
92-
// Update to specified version if previously set to default
93-
entry.MinMondooVersion = version
94-
}
95-
if strings.HasPrefix(id, k) {
96-
entry.Platform = &lrcore.LrDocsPlatform{
97-
Name: platformMapping[k],
98-
}
99-
}
100-
}
101-
return entry
102-
}
103-
104-
func mergeFields(version string, entry *lrcore.LrDocsEntry, fields []*lrcore.BasicField) {
105-
if entry == nil && len(fields) > 0 {
106-
entry = &lrcore.LrDocsEntry{}
107-
entry.Fields = map[string]*lrcore.LrDocsField{}
108-
} else if entry == nil {
109-
return
110-
} else if entry.Fields == nil {
111-
entry.Fields = map[string]*lrcore.LrDocsField{}
112-
}
113-
docFields := entry.Fields
114-
for _, f := range fields {
115-
if docFields[f.ID] == nil {
116-
fDoc := &lrcore.LrDocsField{
117-
MinMondooVersion: version,
118-
}
119-
entry.Fields[f.ID] = fDoc
120-
} else if entry.Fields[f.ID].MinMondooVersion == defaultVersionField && version != defaultVersionField {
121-
entry.Fields[f.ID].MinMondooVersion = version
122-
}
123-
// Scrub field version if same as resource
124-
if entry.Fields[f.ID].MinMondooVersion == entry.MinMondooVersion {
125-
entry.Fields[f.ID].MinMondooVersion = ""
126-
}
127-
}
128-
}
129-
13061
var docsJsonCmd = &cobra.Command{
13162
Use: "json",
13263
Short: "convert yaml docs manifest into json",
@@ -157,43 +88,6 @@ func runDocsYamlCmd(lrFile string, headerFile string, version string, docsFilePa
15788
return
15889
}
15990

160-
// to ensure we generate the same markdown, we sort the resources first
161-
sort.SliceStable(res.Resources, func(i, j int) bool {
162-
return res.Resources[i].ID < res.Resources[j].ID
163-
})
164-
165-
d := lrcore.LrDocs{
166-
Resources: map[string]*lrcore.LrDocsEntry{},
167-
}
168-
169-
fields := map[string][]*lrcore.BasicField{}
170-
isPrivate := map[string]bool{}
171-
for i := range res.Resources {
172-
id := res.Resources[i].ID
173-
isPrivate[id] = res.Resources[i].IsPrivate
174-
d.Resources[id] = nil
175-
if res.Resources[i].Body != nil {
176-
basicFields := []*lrcore.BasicField{}
177-
for _, f := range res.Resources[i].Body.Fields {
178-
if f.BasicField != nil {
179-
basicFields = append(basicFields, f.BasicField)
180-
}
181-
}
182-
fields[id] = basicFields
183-
}
184-
}
185-
186-
// default behaviour is to output the result on cli
187-
if docsFilePath == "" {
188-
data, err := yaml.Marshal(d)
189-
if err != nil {
190-
log.Fatal().Err(err).Msg("could not marshal docs")
191-
}
192-
193-
fmt.Println(string(data))
194-
return
195-
}
196-
19791
// if an file was provided, we check if the file exist and merge existing content with the new resources
19892
// to ensure that existing documentation stays available
19993
var existingData lrcore.LrDocs
@@ -208,28 +102,28 @@ func runDocsYamlCmd(lrFile string, headerFile string, version string, docsFilePa
208102
if err != nil {
209103
log.Fatal().Err(err).Msg("could not load yaml data")
210104
}
105+
}
211106

212-
log.Info().Msg("merge content")
213-
for k := range existingData.Resources {
214-
v := existingData.Resources[k]
215-
d.Resources[k] = v
216-
}
107+
docs, err := res.GenerateDocs(version, defaultVersionField, existingData)
108+
if err != nil {
109+
log.Fatal().Err(err).Msg("could not generate docs")
217110
}
111+
// default behaviour is to output the result on cli
112+
if docsFilePath == "" {
113+
data, err := yaml.Marshal(docs)
114+
if err != nil {
115+
log.Fatal().Err(err).Msg("could not marshal docs")
116+
}
218117

219-
// ensure default values and fields are set
220-
for k := range d.Resources {
221-
d.Resources[k] = ensureDefaults(k, d.Resources[k], version)
222-
mergeFields(version, d.Resources[k], fields[k])
223-
// Merge in other doc fields from core.lr
224-
d.Resources[k].IsPrivate = isPrivate[k]
118+
fmt.Println(string(data))
119+
return
225120
}
226121

227122
// generate content
228-
data, err := yaml.Marshal(d)
123+
data, err := yaml.Marshal(docs)
229124
if err != nil {
230125
log.Fatal().Err(err).Msg("could not marshal docs")
231126
}
232-
233127
// add license header
234128
var headerTpl *template.Template
235129
if headerFile != "" {

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

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

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import (
1515

1616
var cfgFile string
1717

18+
func init() {
19+
cobra.OnInitialize(initConfig)
20+
21+
// Here you will define your flags and configuration settings.
22+
// Cobra supports persistent flags, which, if defined here,
23+
// will be global for your application.
24+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Set config file path (default $HOME/.lr.yaml)")
25+
}
26+
1827
// rootCmd represents the base command when called without any subcommands
1928
var rootCmd = &cobra.Command{
2029
Use: "cli",
@@ -34,15 +43,6 @@ func Execute() {
3443
}
3544
}
3645

37-
func init() {
38-
cobra.OnInitialize(initConfig)
39-
40-
// Here you will define your flags and configuration settings.
41-
// Cobra supports persistent flags, which, if defined here,
42-
// will be global for your application.
43-
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Set config file path (default $HOME/.lr.yaml)")
44-
}
45-
4646
// initConfig reads in config file and ENV variables if set.
4747
func initConfig() {
4848
if cfgFile != "" {

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)