-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmain.go
244 lines (205 loc) · 7.29 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/giantswarm/microerror"
"github.com/spf13/cobra"
"github.com/giantswarm/crd-docs-generator/pkg/annotations"
"github.com/giantswarm/crd-docs-generator/pkg/config"
"github.com/giantswarm/crd-docs-generator/pkg/crd"
"github.com/giantswarm/crd-docs-generator/pkg/git"
"github.com/giantswarm/crd-docs-generator/pkg/output"
)
// CRDDocsGenerator represents an instance of this command line tool, it carries
// the cobra command which runs the process along with configuration parameters
// which come in as flags on the command line.
type CRDDocsGenerator struct {
// Internals.
rootCommand *cobra.Command
// Settings/Preferences
// Path to the config file
configFilePath string
}
const (
// Target path for our clone of the apiextensions repo.
repoFolder = "/tmp/gitclone"
// Default path for Markdown output (if not given in config)
defaultOutputPath = "./output"
)
func main() {
var err error
var crdDocsGenerator CRDDocsGenerator
{
c := &cobra.Command{
Use: "crd-docs-generator",
Short: "crd-docs-generator is a command line tool for generating markdown files that document Giant Swarm's custom resources",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return generateCrdDocs(crdDocsGenerator.configFilePath)
},
}
c.PersistentFlags().StringVar(&crdDocsGenerator.configFilePath, "config", "./config.yaml", "Path to the configuration file.")
crdDocsGenerator.rootCommand = c
}
if err = crdDocsGenerator.rootCommand.Execute(); err != nil {
printStackTrace(err)
os.Exit(1)
}
}
// generateCrdDocs is the function called from our main CLI command.
func generateCrdDocs(configFilePath string) error {
configuration, err := config.Read(configFilePath)
if err != nil {
return microerror.Mask(err)
}
// Full names and versions of CRDs found, to avoid duplicates.
crdNameAndVersion := make(map[string]bool)
outputPath := configuration.OutputPath
if outputPath == "" {
outputPath = defaultOutputPath
}
// Loop over configured repositories
defer os.RemoveAll(repoFolder)
for _, sourceRepo := range configuration.SourceRepositories {
// List of source YAML files containing CRD definitions.
crdFiles := make(map[string]bool)
log.Printf("INFO - repo %s (%s)", sourceRepo.ShortName, sourceRepo.URL)
clonePath := repoFolder + "/" + sourceRepo.Organization + "/" + sourceRepo.ShortName
// Clone the repositories containing CRDs
log.Printf("INFO - repo %s - cloning repository", sourceRepo.ShortName)
err = git.CloneRepositoryShallow(
sourceRepo.Organization,
sourceRepo.ShortName,
sourceRepo.CommitReference,
clonePath)
if err != nil {
return microerror.Mask(err)
}
// Collect CRD YAML files
for _, crdPath := range sourceRepo.CRDPaths {
thisCRDFolder := clonePath + "/" + crdPath
err = filepath.Walk(thisCRDFolder, func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".yaml") {
crdFiles[path] = true
}
return nil
})
if err != nil {
return microerror.Mask(err)
}
}
// Collect annotation info
var repoAnnotations []annotations.CRDAnnotationSupport
for _, annotationsPath := range sourceRepo.AnnotationsPath {
thisAnnotationsFolder := clonePath + "/" + annotationsPath
log.Printf("INFO - repo %s - collecting annotations in %s", sourceRepo.ShortName, thisAnnotationsFolder)
a, err := annotations.Collect(thisAnnotationsFolder)
if err != nil {
log.Printf("ERROR - repo %s - collecting annotations in %s yielded error %#v", sourceRepo.ShortName, thisAnnotationsFolder, err)
}
repoAnnotations = append(repoAnnotations, a...)
}
crdFilesSlice := []string{}
for crdFile := range crdFiles {
crdFilesSlice = append(crdFilesSlice, crdFile)
}
sort.Strings(crdFilesSlice)
for _, crdFile := range crdFilesSlice {
log.Printf("INFO - repo %s - reading CRDs from file %s", sourceRepo.ShortName, crdFile)
crds, err := crd.Read(crdFile)
if err != nil {
log.Printf("WARN - something went wrong in crd.Read for file %s: %#v", crdFile, err)
}
for i := range crds {
// Collect versions of this CRD
versions := []string{}
for _, v := range crds[i].Spec.Versions {
fullKey := crds[i].Name + "_" + v.Name
_, exists := crdNameAndVersion[fullKey]
if exists {
log.Printf("WARN - repo %s - file %s provides CRD %s version %s which is already added - skipping", sourceRepo.ShortName, crdFile, crds[i].Name, v.Name)
continue
}
crdNameAndVersion[fullKey] = true
versions = append(versions, v.Name)
}
if len(versions) == 0 {
log.Printf("WARN - repo %s - CRD %s in file %s provides no versions - skipping", sourceRepo.ShortName, crds[i].Name, crdFile)
continue
}
log.Printf("INFO - repo %s - processing CRD %s with versions %v", sourceRepo.ShortName, crds[i].Name, versions)
// Skip hidden CRDs and CRDs with missing metadata
meta, ok := sourceRepo.Metadata[crds[i].Name]
if !ok {
log.Printf("WARN - repo %s - skipping %s as no metadata found", sourceRepo.ShortName, crds[i].Name)
continue
}
if meta.Hidden {
log.Printf("INFO - repo %s - skipping %s as hidden by configuration", sourceRepo.ShortName, crds[i].Name)
continue
}
// Get at most one example CR for each version of this CRD
exampleCRs := make(map[string]string)
for _, version := range versions {
found := false
for _, crPath := range sourceRepo.CRPaths {
crFilePath := fmt.Sprintf("%s/%s/%s_%s_%s.yaml", clonePath, crPath, crds[i].Spec.Group, version, crds[i].Spec.Names.Singular)
if _, err := os.Stat(crFilePath); errors.Is(err, os.ErrNotExist) {
continue
}
exampleCR, err := os.ReadFile(crFilePath)
if err != nil {
log.Printf("ERROR - repo %s - example CR %s could not be read: %s", sourceRepo.ShortName, crFilePath, err)
} else {
found = true
exampleCRs[version] = strings.TrimSpace(string(exampleCR))
break
}
}
if !found {
log.Printf("WARN - repo %s - No example CR found for %s version %s ", sourceRepo.ShortName, crds[i].Name, version)
}
}
templatePath := path.Dir(configFilePath) + "/" + configuration.TemplatePath
crdAnnotations := annotations.FilterForCRD(repoAnnotations, crds[i].Name, "")
_, err = output.WritePage(
crds[i],
crdAnnotations,
meta,
exampleCRs,
outputPath,
sourceRepo.URL,
sourceRepo.CommitReference,
templatePath)
if err != nil {
log.Printf("WARN - repo %s - something went wrong in WriteCRDDocs: %#v", sourceRepo.ShortName, err)
}
}
}
}
return nil
}
func printStackTrace(err error) {
fmt.Println("\n--- Stack Trace ---")
var stackedError microerror.JSONError
jsonErr := json.Unmarshal([]byte(microerror.JSON(err)), &stackedError)
if jsonErr != nil {
fmt.Println("Error when trying to Unmarshal JSON error:")
log.Printf("%#v", jsonErr)
fmt.Println("\nOriginal error:")
log.Printf("%#v", err)
}
for i, j := 0, len(stackedError.Stack)-1; i < j; i, j = i+1, j-1 {
stackedError.Stack[i], stackedError.Stack[j] = stackedError.Stack[j], stackedError.Stack[i]
}
for _, entry := range stackedError.Stack {
log.Printf("%s:%d", entry.File, entry.Line)
}
}