Skip to content

Commit f730f2b

Browse files
Merge pull request #95 from martinkunc/refactoring-collector-add-doc
Bug 1825831: Refactoring collector, add Doc and doc generator
2 parents 85a0b12 + fc6889b commit f730f2b

File tree

86 files changed

+11237
-252
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+11237
-252
lines changed

Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ vet:
1616
lint:
1717
golint $$(go list ./... | grep -v /vendor/)
1818

19+
gen-doc:
20+
go run cmd/gendoc/main.go --out=docs/gathered-data.md
21+
1922
vendor:
2023
go mod tidy
2124
go mod vendor

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This cluster operator gathers anonymized system configuration and reports it to
88
* ClusterOperator objects
99
* All non-secret global config (hostnames and URLs anonymized)
1010

11+
The list of all collected data with description, location in produced archive and link to Api and some examples is at [docs/gathered-data.md](docs/gathered-data.md)
12+
13+
The resulting data is packed in .tar.gz archive with folder structure indicated in the document. Example of such archive is at [docs/insights-archive-sample](docs/insights-archive-sample).
14+
1115
## Building
1216

1317
To build the operator, install Go 1.11 or above and run:
@@ -42,3 +46,13 @@ It is also possible to specify CLI options for Go test. For example, if you need
4246

4347
Insights Operator is part of Red Hat OpenShift Container Platform. For product-related issues, please
4448
file a ticket [in Red Hat Bugzilla](https://bugzilla.redhat.com/enter_bug.cgi?product=OpenShift%20Container%20Platform&component=Insights%20Operator) for "Insights Operator" component.
49+
50+
## Generating the document with gathered data
51+
The document docs/gathered-data contains list of collected data, the Api and some examples. The document is generated from package sources by looking for Gather... methods.
52+
If for any GatherXXX method exists its method which returns example with name ExampleXXX, the generated example is added to document with the size in bytes.
53+
54+
55+
To start generating the document run:
56+
```
57+
make gen-doc
58+
```

cmd/gendoc/main.go

+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"go/ast"
7+
"go/build"
8+
"go/parser"
9+
"go/token"
10+
"math/rand"
11+
"os"
12+
"os/exec"
13+
"path/filepath"
14+
"regexp"
15+
"sort"
16+
"strings"
17+
"time"
18+
)
19+
20+
var (
21+
expPkg string
22+
mdfname string
23+
mdf *os.File
24+
randSource = rand.NewSource(time.Now().UnixNano())
25+
reGather = regexp.MustCompile(`^(Gather)(.*)`)
26+
reExample = regexp.MustCompile(`^(Example)(.*)`)
27+
)
28+
29+
func init() {
30+
flag.StringVar(&expPkg, "gatherpkg", "gather", "Package where to find Gather methods")
31+
flag.StringVar(&mdfname, "out", "gathered-data.md", "File to which MD doc will be generated")
32+
}
33+
34+
type DocBlock struct {
35+
Doc string
36+
Examples map[string]string
37+
}
38+
39+
func main() {
40+
flag.Parse()
41+
var err error
42+
mdf, err = os.Create(mdfname)
43+
if err != nil {
44+
fmt.Println(err)
45+
return
46+
}
47+
defer mdf.Close()
48+
cleanRoot := "./"
49+
50+
md := map[string]*DocBlock{}
51+
err = walkDir(cleanRoot, md)
52+
if err != nil {
53+
fmt.Print(err)
54+
}
55+
// second pass will gather Sample..
56+
err = walkDir(cleanRoot, md)
57+
if err != nil {
58+
fmt.Print(err)
59+
}
60+
keys := make([]string, 0, len(md))
61+
for k := range md {
62+
keys = append(keys, k)
63+
}
64+
sort.Strings(keys)
65+
mdf.WriteString(fmt.Sprintf("This document is auto-generated by `make gen-doc`\n\n"))
66+
for _, k := range keys {
67+
mdf.WriteString(fmt.Sprintf("## %s\n\n", k))
68+
mdf.WriteString(fmt.Sprintf("%s\n\n", md[k].Doc))
69+
if len(md[k].Examples) > 0 {
70+
size := 0
71+
for _, e := range md[k].Examples {
72+
size = len(e)
73+
}
74+
size = size / len(md[k].Examples)
75+
mdf.WriteString(fmt.Sprintf("Output raw size: %d\n\n", size))
76+
77+
mdf.WriteString(fmt.Sprintf("### Examples\n\n"))
78+
for n, e := range md[k].Examples {
79+
mdf.WriteString(fmt.Sprintf("#### %s\n", n))
80+
mdf.WriteString(fmt.Sprintf("%s\n\n", e))
81+
}
82+
}
83+
}
84+
fmt.Printf("Done")
85+
}
86+
87+
func walkDir(cleanRoot string, md map[string]*DocBlock) error {
88+
expPath := ""
89+
fset := token.NewFileSet() // positions are relative to fset
90+
return filepath.Walk(cleanRoot, func(path string, info os.FileInfo, e1 error) error {
91+
if !info.IsDir() {
92+
return nil
93+
}
94+
if expPath != "" {
95+
// filter only wanted path under our package
96+
if !strings.Contains(path, expPath) {
97+
return nil
98+
}
99+
}
100+
d, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
101+
if err != nil {
102+
fmt.Println(err)
103+
return nil
104+
}
105+
106+
for astPackageName, astPackage := range d {
107+
if astPackageName != expPkg && expPath == "" {
108+
continue
109+
}
110+
if expPath == "" && len(astPackage.Files) > 0 {
111+
firstKey := ""
112+
for key := range astPackage.Files {
113+
firstKey = key
114+
break
115+
}
116+
if firstKey != "" {
117+
expPath = filepath.Dir(firstKey)
118+
}
119+
}
120+
121+
ast.Inspect(astPackage, func(n ast.Node) bool {
122+
// handle function declarations
123+
fn, ok := n.(*ast.FuncDecl)
124+
if ok {
125+
gatherMethodWithSuff := reGather.ReplaceAllString(fn.Name.Name, "$2")
126+
_, ok2 := md[gatherMethodWithSuff]
127+
if !ok2 && fn.Name.IsExported() && strings.HasPrefix(fn.Name.Name, "Gather") && len(fn.Name.Name) > len("Gather") {
128+
doc := fn.Doc.Text()
129+
md[gatherMethodWithSuff] = parseDoc(fn.Name.Name, doc)
130+
fmt.Printf(fn.Name.Name + "\n")
131+
}
132+
// Example methods will have Example prefix, and might have additional case suffix:
133+
// ExampleMostRecentMetrics_case1, we will remove Example prefix
134+
exampleMethodWithSuff := reExample.ReplaceAllString(fn.Name.Name, "$2")
135+
var gatherMethod = ""
136+
for m := range md {
137+
if strings.HasPrefix(exampleMethodWithSuff, m) {
138+
gatherMethod = m
139+
break
140+
}
141+
}
142+
143+
if gatherMethod != "" && fn.Name.IsExported() && strings.HasPrefix(fn.Name.Name, "Example") && len(fn.Name.Name) > len("Example") {
144+
145+
// Do not execute same method twice
146+
_, ok := md[exampleMethodWithSuff].Examples[exampleMethodWithSuff]
147+
if !ok {
148+
methodFullpackage := getPackageName(cleanRoot, astPackage)
149+
150+
output, err := execExampleMethod(methodFullpackage, astPackageName, fn.Name.Name)
151+
if err != nil {
152+
fmt.Printf("Error when running Example in package %s method %s\n", methodFullpackage, fn.Name.Name)
153+
fmt.Println(err)
154+
fmt.Println(output)
155+
return true
156+
}
157+
if md[exampleMethodWithSuff].Examples == nil {
158+
md[exampleMethodWithSuff].Examples = map[string]string{}
159+
}
160+
md[exampleMethodWithSuff].Examples[exampleMethodWithSuff] = string(output)
161+
}
162+
fmt.Printf(fn.Name.Name + "\n")
163+
}
164+
}
165+
return true
166+
})
167+
}
168+
return nil
169+
})
170+
}
171+
172+
// getPackageName generates full package name from asp.Package
173+
// astRoot the relative path where ast.Package was parsed from, because ast.Package is relative to astRoot path
174+
// f ast.Package with containing files
175+
// The returned full package is inferred from files in ast.Package and GOPATH
176+
func getPackageName(astRoot string, f *ast.Package) string {
177+
firstKey := ""
178+
var mypkg string
179+
for key := range f.Files {
180+
firstKey = key
181+
break
182+
}
183+
if firstKey != "" {
184+
abspath, _ := filepath.Abs(filepath.Join(astRoot, filepath.Dir(firstKey)))
185+
186+
mypkg = strings.TrimPrefix(abspath, filepath.Join(goPath(), "src"))
187+
mypkg = strings.TrimLeft(mypkg, string(filepath.Separator))
188+
}
189+
return mypkg
190+
}
191+
192+
func goPath() string {
193+
gopath := os.Getenv("GOPATH")
194+
if gopath == "" {
195+
gopath = build.Default.GOPATH
196+
}
197+
return gopath
198+
}
199+
200+
// execExampleMethod executes the method by starting go run and capturing the produced standard output
201+
func execExampleMethod(methodFullPackage, methodPackage, methodName string) (string, error) {
202+
f := createRandom()
203+
204+
tf, err := os.Create(f)
205+
if err != nil {
206+
fmt.Println(err)
207+
return "", err
208+
}
209+
defer func() {
210+
err := os.Remove(f)
211+
if err != nil {
212+
fmt.Print(err)
213+
}
214+
}()
215+
tf.WriteString(fmt.Sprintf(`package main
216+
import "%s"
217+
import "fmt"
218+
219+
func main() {
220+
str, _ := %s.%s()
221+
fmt.Print(str)
222+
}
223+
`, methodFullPackage, methodPackage, methodName))
224+
tf.Close()
225+
cmd := exec.Command("go", "run", "./"+f)
226+
output, err := cmd.CombinedOutput()
227+
return string(output), err
228+
}
229+
230+
// createRandom creates a random non existing file name in current folder
231+
func createRandom() string {
232+
var f string
233+
for {
234+
f = fmt.Sprintf("sample%d.go", randSource.Int63())
235+
_, err := os.Stat(f)
236+
if os.IsNotExist(err) {
237+
break
238+
}
239+
}
240+
return f
241+
}
242+
243+
func parseDoc(method, doc string) *DocBlock {
244+
if strings.HasPrefix(doc, method) {
245+
doc = strings.TrimLeft(doc, method)
246+
}
247+
doc = strings.TrimLeft(doc, " ")
248+
249+
db := &DocBlock{Doc: doc}
250+
return db
251+
}

0 commit comments

Comments
 (0)