/
definitions.go
660 lines (557 loc) · 15.5 KB
/
definitions.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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
// Package cmd from gobra
// types and methods for blueprint definitions file
package cmd
import (
"errors"
"fmt"
"os"
// "reflect"
"regexp"
"strings"
)
var StaticDefinitionFileVersion = "v0.9.0"
// Tables structure for user defined tables that need
// to be generated
type Tables struct {
// TableName is the name of the table to create.
// This is really just a sub-object
// It could be meta-data, user, blah...
//
TableName string `yaml:"table-name"`
// TableType
// JSONB Supported today
// SQLTable Future
//
// If TableRole is Secondary, TableType is ignored
// if present and the table will be added as a sub
// structure
//
TableType string `yaml:"table-type"`
// ParentTable
// Nest this table under the named parent
ParentTable string `yaml:"parent-tables"`
// A list of table columns or object attributes
Columns []struct {
// Name of the column
Name string `yaml:"name"`
// Modifiers to apply when marshalling
// omitempty or string
//
Modifiers string `yaml:"modifiers"`
// MappedName to use when marshalling
// If empty, map to lowercase of name
//
MappedName string `yaml:"mapped-name"`
// Constraints such as required
// Valid swagger 2.0 validation
//
Constraints string `yaml:"constraints"`
// TODO: need to map this to array/map for validation
// Type such as boolean, string, float, or integer
// valid types: Stick to JSON for now
// string
// number
// integer
// boolean
// time
// null
Type string `yaml:"type"`
} `yaml:"columns"`
}
// Community files to be included
// For example, CONTRIBUTING.md
type Community struct {
CommunityFiles []struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
Src string `yaml:"src"`
Md5 string `yaml:"md5,omitempty"`
} `yaml:"community-files"`
Description string `yaml:"description"`
}
// Info defines information about the services and organization
type Info struct {
APIVersion string `yaml:"api-version"`
ID string `yaml:"id"`
Name string `yaml:"name"`
Organization string `yaml:"organization"`
GitHubOrg string `yaml:"githuborg"`
SonarCloudOrg string `yaml:"sonarcloudorg"`
ReleaseStatus string `yaml:"release-status"`
Version string `yaml:"version"`
}
// Dependencies that this service requires
type Dependencies []struct {
Command string `yaml:"command"`
Comments string `yaml:"comments"`
DockerCockroahdb interface{} `yaml:"docker-cockroahdb,omitempty"`
Image string `yaml:"image"`
Name string `yaml:"name"`
Ports []struct {
External string `yaml:"external"`
Internal string `yaml:"internal"`
} `yaml:"ports"`
Volumes []interface{} `yaml:"volumes"`
DockerKafka interface{} `yaml:"docker-kafka,omitempty"`
Topics []Topic `yaml:"topics,omitempty"`
}
// Topic defines a Kafka topic and its partition and replication counts
type Topic struct {
// Value is the topic name
Value string `json:"value"`
// Partitions is the number of partions
Partitions int `json:"partitions"`
// ReleaseStatus is the replication factor for this topic
Replication int `json:"replication"`
}
// Maintainer contact information
type Maintainer struct {
Email string `yaml:"email"`
Name string `yaml:"name"`
Slack string `yaml:"slack"`
Web string `yaml:"web"`
}
// ProjectFiles blueprint files to be included
type ProjectFiles struct {
Description string `yaml:"description"`
Name string `yaml:"name"`
Path string `yaml:"path"`
Src string `yaml:"src"`
}
// Badge are links with graphics to be included in
// doc/service.html file. These go to CI test results
type Badge struct {
Enable bool `yaml:"enable"`
Link string `yaml:"link"`
Name string `yaml:"name"`
}
// ConfigurationFile where the configuration can be found
type ConfigurationFile struct {
ArtifactsDir string `yaml:"artifacts-dir"`
Name string `yaml:"name"`
Path string `yaml:"path"`
Src string `yaml:"src"`
}
// KubeConfig
type KubeConfig struct {
// Namespace to use when constructing URLs
Namespace string `yaml:"namespace"`
// Liveness endpoint name for k8s checks
Liveness string `yaml:"liveness"`
// Readiness endpoint name for k8s checks
Readiness string `yaml:"readiness"`
// Metrics endpoint name for k8s checks
Metrics string `yaml:"metrics"`
// Management endpoint
Management string `yaml:"management"`
// Explain endpoint
Explain string `yaml:"explain"`
}
// Integrations CI/CD tools
type Integrations struct {
Badges []string `yaml:"shields,omitempty"`
Name string `yaml:"name"`
Enabled bool `yaml:"enable"`
// TODO: Needs to be more generic
//
SonarCloudConfig struct {
// A sonarcloud access token
Login string `yaml:"login"`
// Project key should be same as the name
Key string `yaml:"key"`
Options struct {
Badges []string `yaml:"shields"`
Coverage struct {
Enable bool `yaml:"enable"`
Report string `yaml:"report"`
} `yaml:"coverage"`
GoSec struct {
Enable bool `yaml:"enable"`
Report string `yaml:"report"`
} `yaml:"go-sec"`
Lint struct {
Enable bool `yaml:"enable"`
Report string `yaml:"report"`
} `yaml:"lint"`
} `yaml:"options"`
} `yaml:"sonar-cloud-config,omitempty"`
ConfigurationFile ConfigurationFile `yaml:"configuration-file,omitempty"`
}
// Query parameter
type queryParm struct {
Name string `json:"name"`
DataType string `json:"datatype"`
Description string `json:"description"`
}
type httpMethod struct {
// Method HTTP method
Method string `json:"method"`
// QP query parameters
QP []queryParm `json:"qp"`
// Headers Method specific headers
Headers []HTTPHeader `yaml:"headers"`
}
// Endpoints
type endPoint struct {
// Name for this endpoint
Name string `yaml:"name"`
// Methods list of HTTP methods
Methods []httpMethod `yaml:"methods"`
// Headers endpoint specific headers
Headers []HTTPHeader `yaml:"headers"`
// Port to listen on
Port string `yaml:"port"`
// Host address to listen on
Host string `yaml:"host"`
}
// HTTPHeader header name and value
type HTTPHeader struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}
// Project information
type Project struct {
TLD string `yaml:"top_level_domain"`
Description string `yaml:"description"`
Dependencies Dependencies `yaml:"dependencies"`
License string `yaml:"license"`
SchedulerName string `yaml:"scheduler_name"`
Maintainer Maintainer `yaml:"maintainer"`
ProjectFiles []ProjectFiles `yaml:"project-files"`
Integrations []Integrations `yaml:"integrations"`
Kubernetes KubeConfig `yaml:"kubernetes"`
Endpoints []endPoint `yaml:"endpoints"`
Loggers []Logger `yaml:"loggers"`
Blocks []Block `yaml:"blocks"`
Config Config `yaml:"configuration"`
}
type Config struct {
HTTPHost string `yaml:"http-host"`
HTTPPort string `yaml:"http-port"`
}
type Go struct {
DependencyManager string `yaml:"dependency-manager"`
}
// Core capabilities to enable / configure
type Core struct {
Loggers []Logger `yaml:"loggers"`
}
type bpDef struct {
DefinitionFileVersion string
DefinitionFile string `yaml:"definitionFile"`
TableList []Tables `yaml:"tables"`
Community Community `yaml:"community"`
Info Info `yaml:"info"`
Project Project `yaml:"project"`
}
// Define constants for error types
// iota starts at 0, next constant is iota + 1
const (
INVALIDCOLUMNTYPE = iota
INVALIDCONTENT
INVALIDTABLENAME
NOPARENT
NOMAPPEDNAME
INVALIDTABLETYPE
INVALIDCOLUMNNAME
INVALIDCONSTRAINT
INVALIDMODIFIER
INVALIDARRAY
INVALIDSERVICENAME
UNKOWN
)
// tblDefError
// structure for returning table definition errors
//
type tblDefError struct {
errorType int
errorMessage string
tableName string
}
// ErrList is a list of table definition error messages.
// Processing is done on all errors instead of exiting for
// every error.
var ErrList []tblDefError //Prior comment for exported format.
// tlbDefError
// implements error.Error() interface
//
func (d *tblDefError) Error() string {
e := fmt.Sprintf("Table: %v, Error number: %v, %v\n",
d.tableName,
d.errorType,
d.errorMessage)
return e
}
func (d *bpDef) setErrorList(msgNum int, msg string, tName string) {
e := tblDefError{
errorType: msgNum,
errorMessage: msg,
tableName: tName,
}
ErrList = append(ErrList, e)
}
// bpTableItem
type bpTableItem struct {
// The name of this table
Name string
Root bool
// Children: a list of table items containing
// child tables
Children []*bpTableItem
// IsList means this is a []Type talbe
IsList bool
}
// devineOrder: Determine primary table and its
// children. Generate an error if no primary is
// found or more than one primary is found
// TODO: The above logic needs to be specific to
// the type of service build built
func (d *bpDef) devineOrder() bpTableItem {
// ptName "" means this table does not have
// a parent
ptName := ""
// Get primary table and make sure it is the only primary
x := d.findTables(ptName)
if len(x) == 0 {
fmt.Println("No primary table found")
os.Exit(-1)
} else if len(x) > 1 {
fmt.Println("More than primary table found: ", len(x))
os.Exit(-1)
} else {
pt := x[0]
ptName = pt.Name
}
//fmt.Println(d.TableList)
d.addChildren(&x[0])
//d.walkOrder(x[0])
return x[0]
}
// walkOrder: Given a parent, print out all of its
// children
func (d *bpDef) walkOrder(item bpTableItem) {
if len(item.Children) > 0 {
for _, v := range item.Children {
fmt.Printf("Parent: %v Child: %v\n", item.Name, v.Name)
d.walkOrder(*v)
}
} else {
fmt.Printf("Parent: %v Child: no children\n", item.Name)
}
return
}
// addChildren: Add children to a parent, then
// add any children they may have recursively
func (d *bpDef) addChildren(parent *bpTableItem) {
c := d.findTables(parent.Name)
if len(c) == 0 {
return
}
for _, v := range c {
nc := v
parent.Children = append(parent.Children, &nc)
d.addChildren(&nc)
}
return
}
// findTables: Find primary parent table, or
// children for a given table
func (d *bpDef) findTables(parent string) []bpTableItem {
rlist := []bpTableItem{}
// tlist := d.tables()
for _, t := range d.TableList {
if t.ParentTable == parent {
c := make([]*bpTableItem, 0, 20)
var isRoot = false
if parent == "" {
isRoot = true
}
var isList = false
if strings.ToLower(t.TableType) == "list" {
isList = true
}
newrec := bpTableItem{t.TableName, isRoot, c, isList}
rlist = append(rlist, newrec)
}
}
return rlist
}
// tables(): return a pointer(a copy?) to definitions Tables
func (d *bpDef) tables() []Tables {
return d.TableList
}
// Search for Table by name
func (d *bpDef) tableByName(name string) (Tables, error) {
e := Tables{}
for _, v := range d.TableList {
if v.TableName == name {
return v, nil
}
}
return e, errors.New("table not found")
}
func (d *bpDef) badges() []string {
var badgelist []string
for _, rec := range d.Project.Integrations {
if len(rec.Badges) > 0 {
badgelist = append(badgelist, rec.Badges...)
}
if strings.ToLower(rec.Name) == "sonarcloud" &&
len(rec.SonarCloudConfig.Options.Badges) > 0 {
badgelist = append(badgelist, rec.SonarCloudConfig.Options.Badges...)
}
}
return badgelist
}
func (d *bpDef) findIntegration(name string) Integrations {
for _, rec := range d.Project.Integrations {
if strings.ToLower(rec.Name) == strings.ToLower(name) {
return rec
}
}
a := Integrations{}
return a
}
func (d *bpDef) BadgesToString() string {
badges := ""
/*
for _, b := range d.badges() {
fmt.Println(b)
if b.Enable == true {
badges += b.Link + "\n"
}
}
*/
return badges
}
//Valid the table(s) definition, and other YAML defaults needed
//for anticipated execution
func (d *bpDef) Validate() (errCount int) {
const badMicroserviceName = "yourMicroserviceName"
const pavedroadSonarTestOrg = "acme-demo"
// TODO(sgayle): This defined an empty error message and assigned it
// to LastErr. That caused and error to allways be returned
//Doing YAML default test first
//Blueprint default microservice should be changed
//if sonar cloud testing under pavedroadSonarTestOrg is
//needed.
if (d.Info.Name == defMicroserviceName) && (d.Info.Organization == pavedroadSonarTestOrg) {
d.setErrorList(INVALIDSERVICENAME, "Sonar cloud microservice name change expected.", "")
}
// Do all tables and report all potential errors
for _, t := range d.tables() {
// Metadata validation
e := d.validateTableMetaData(t)
if e > 0 {
return e
}
// Column validation
e = d.validateTableColumns(t)
if e > 0 {
return e
}
}
return errCount
}
// validateTableMetaData
// name not blank, type is supported, parent table exists
// Table name only have allowed characters
// **All table names should be unique (not case sensitive)
// Table name length
func (d *bpDef) validateTableMetaData(t Tables) (errCount int) {
var validTypes = []string{"JSONB", "OBJECT", "LIST"}
const maxLen = 60
// Make sure table name is set
if t.TableName == "" {
d.setErrorList(INVALIDTABLENAME, "Missing table name", "")
errCount++
} else {
if len(t.TableName) > maxLen {
e := fmt.Sprintf("Table name length cannot be greater than %v", maxLen)
d.setErrorList(INVALIDTABLENAME, e, t.TableName)
errCount++
}
// Use simple regex until more specific requirements
// and security review.
// Must be modified to support specific target databases
// Looked at specifications for Cockroachdb
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]*$`, t.TableName)
if !matched {
d.setErrorList(INVALIDTABLENAME, "Bad table name: ["+t.TableName+"]", t.TableName)
errCount++
}
}
// Make sure it is a valid type
if t.ParentTable == "" {
isValidType := false
for _, m := range validTypes {
if strings.ToUpper(t.TableType) == m {
isValidType = true
break
}
}
if !isValidType {
d.setErrorList(INVALIDTABLETYPE, "Bad table type: ["+t.TableType+"]", t.TableName)
errCount += 1
}
}
// If a parent is specified make sure it exists
if t.ParentTable != "" {
_, e := d.tableByName(t.ParentTable)
if e != nil {
d.setErrorList(NOPARENT, "Parent table not found: ["+t.ParentTable+"]", t.TableName)
errCount += 1
}
}
return errCount
}
func (d *bpDef) validateTableColumns(t Tables) (errCount int) {
var convName string
// validate:
// - Name *
// - Modifiers
// - MappedName
// - Constraints
// - Type *
//
for _, v := range t.Columns {
//Check the column name
if v.Name == "" {
d.setErrorList(INVALIDCOLUMNNAME, "Missing column name", t.TableName)
errCount += 1
} else {
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]*$`, v.Name)
if !matched {
d.setErrorList(INVALIDCOLUMNNAME, "Bad column name: ["+v.Name+"]", t.TableName)
errCount += 1
}
}
//Check the column types
m := mappedTypes{}
convName = strings.ToLower(v.Type)
if !m.validInputType(convName) {
d.setErrorList(INVALIDCOLUMNTYPE, "Invalid column type: ["+v.Type+"]", t.TableName)
errCount += 1
}
//Check the mapped Name
//This wouldn't be an error if it is the functionality
//required.
if v.MappedName == "" {
v.MappedName = strings.ToLower(v.Name)
d.setErrorList(NOMAPPEDNAME, "Column : ["+v.MappedName+"] had no mapped name.", t.TableName)
errCount += 1
}
}
return errCount
}
// isStringInList
// Should move to a generic function list
//
func isStringInList(l []string, s string) bool {
for _, v := range l {
if v == s {
return true
}
}
return false
}