forked from oVirt/go-ovirt-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrest.go
279 lines (265 loc) · 6.55 KB
/
rest.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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"text/template"
)
// restItem is the data structure passed to code generation templates s a root object.
type restItem struct {
// Name is the human-readable name for this item. It should be written lower case and with spaces.
Name string
// Object is the name of the item as it is facing outwards from the go-ovirt-client.
Object string
// ID is the SDK identifier for this item. It must be capitalized.
ID string
// SecondaryID is the secondary ID of this item, which is sometimes required when the SDK uses a different name for
// an object. This is the case for VnicProfile vs. Profile, which refer to the same object. Defaults to the same as
// ID.
SecondaryID string
// IDType is the type of the ID field. Defaults to "string".
IDType string
}
func main() {
name, id, secondaryID, object, tplDir, targetDir, nofmt, nolint, idType := getParameters()
name = strings.TrimSpace(name)
if name == "" {
_, _ = fmt.Fprintf(os.Stderr, "The -n parameter is required.\n\n")
flag.Usage()
os.Exit(1)
}
id = strings.TrimSpace(id)
if id == "" {
_, _ = fmt.Fprintf(os.Stderr, "The -i parameter is required.\n\n")
flag.Usage()
os.Exit(1)
}
if object == "" {
object = id
}
if secondaryID == "" {
secondaryID = id
}
restItem := restItem{
name,
object,
id,
secondaryID,
idType,
}
if err := filepath.Walk(
tplDir, func(fn string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
if !strings.HasSuffix(fn, ".tpl") {
return nil
}
return handleTemplateFile(fn, id, targetDir, restItem, nofmt)
},
); err != nil {
log.Fatalln(err)
}
if !nolint {
if err := runGoLint(targetDir); err != nil {
log.Fatalf(
"Failed to run golangci-lint. You can skip this step by passing -nolint in the command line "+
"or setting the NOLINT environment variable. (%v)",
err,
)
}
}
}
func getParameters() (string, string, string, string, string, string, bool, bool, string) {
name := ""
id := ""
secondaryID := ""
object := ""
tplDir := "./codetemplates"
targetDir := "./"
watch := false
nofmt := false
nolint := false
idType := "string"
setupFlags(&name, &id, &secondaryID, &object, &tplDir, &targetDir, &watch, &nofmt, &nolint, &idType)
flag.Usage = func() {
_, _ = fmt.Fprintf(
os.Stderr,
"Usage: go run rest.go OPTIONS\n\n"+
"This file generates REST client calls based on the templates in the \"codetemplates\" directory.\n",
)
flag.PrintDefaults()
os.Exit(1)
}
flag.Parse()
if os.Getenv("NOFMT") != "" {
nofmt = true
}
if os.Getenv("NOLINT") != "" {
nolint = true
}
return name, id, secondaryID, object, tplDir, targetDir, nofmt, nolint, idType
}
// setupFlags sets up the command line flags. This function is annotated with nolint:funlen since there is no reasonable
// way to split this function and still keeping the code simple.
func setupFlags( // nolint:funlen
name *string,
id *string,
secondaryID *string,
object *string,
tplDir *string,
targetDir *string,
watch *bool,
nofmt *bool,
nolint *bool,
idType *string,
) {
flag.StringVar(
name,
"n",
"",
"Pass a human-readable name. E.g. \"storage domain\". Required.",
)
flag.StringVar(
id,
"i",
"",
"Pass an identifier used in the SDK. Must be capitalized. E.g. \"StorageDomain\". Required.",
)
flag.StringVar(
secondaryID,
"s",
"",
"Pass a secondary identifier used in the SDK. Must be capitalized. E.g. \"Profile\".",
)
flag.StringVar(
object,
"o",
"",
"Pass an identifier used in the client. Defaults to the same value as -i. "+
"Must be capitalized. E.g. \"StorageDomain\".",
)
flag.StringVar(
tplDir,
"d",
*tplDir,
fmt.Sprintf(
"Specify a directory for the source templates. Defaults to \"%s\".",
*tplDir,
),
)
flag.StringVar(
targetDir,
"t",
*targetDir,
fmt.Sprintf(
"Specify a target directory the generated files should be written into. Defaults to \"%s\"",
*targetDir,
),
)
flag.StringVar(
idType,
"T",
*idType,
fmt.Sprintf(
"Specify the type of the ID. Defaults to \"%s\"",
*idType,
),
)
flag.BoolVar(
watch,
"w",
false,
"Enable watching templates for changes and update.",
)
flag.BoolVar(
nofmt,
"nofmt",
false,
"Do not run gofmt on resulting file.",
)
flag.BoolVar(
nolint,
"nolint",
false,
"Do not run go golangci-lint on the resulting file.",
)
}
func handleTemplateFile(templateFileName string, id string, targetDir string, restItem restItem, nofmt bool) error {
// We are working through all template files here, so including these files is intentional
// and not a security issue.
fh, err := os.Open(templateFileName) // nolint:gosec
if err != nil {
return fmt.Errorf("failed to open %s (%w)", templateFileName, err)
}
defer func() {
_ = fh.Close()
}()
data, err := ioutil.ReadAll(fh)
if err != nil {
return fmt.Errorf("failed to read %s (%w)", templateFileName, err)
}
targetFileName := path.Base(filepath.ToSlash(strings.TrimSuffix(templateFileName, ".tpl")))
file := fmt.Sprintf(strings.ReplaceAll(targetFileName, "ITEM", "%s"), strings.ToLower(id))
t := path.Join(targetDir, file)
if err := renderTemplate(string(data), t, restItem); err != nil {
return err
}
if !nofmt {
if err := runGoFmt(t); err != nil {
return fmt.Errorf(
"failed to run go fmt on %s. You can skip this step by passing -nofmt in the command line or "+
"setting the NOFMT environment variable. (%w)",
t,
err,
)
}
}
return nil
}
func runGoFmt(t string) error {
cmd := exec.Command("gofmt", "-w", t)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func runGoLint(t string) error {
cmd := exec.Command("golangci-lint", "run", t)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func renderTemplate(tplText string, file string, restItem restItem) error {
tpl, err := template.New("list").Funcs(
map[string]interface{}{
"toLower": func(input string) string {
if len(input) == 0 {
return input
}
return fmt.Sprintf("%s%s", strings.ToLower(input[:1]), input[1:])
},
},
).Parse(tplText)
if err != nil {
return fmt.Errorf("failed to parse list template (%w)", err)
}
fh, err := os.Create(file) //nolint:gosec
if err != nil {
return fmt.Errorf("failed to open %s (%w)", file, err)
}
defer func() {
if err := fh.Close(); err != nil {
panic(fmt.Errorf("failed to close %s (%w)", file, err))
}
}()
if err := tpl.Execute(fh, restItem); err != nil {
return fmt.Errorf("failed to render list template to %s (%w)", file, err)
}
return nil
}