-
Notifications
You must be signed in to change notification settings - Fork 0
/
schemacheck.go
209 lines (176 loc) · 6.12 KB
/
schemacheck.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
package main
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/fatih/color"
flag "github.com/spf13/pflag"
"github.com/xeipuuv/gojsonschema"
"sigs.k8s.io/yaml"
)
// Set default constants for flag usage messages.
const (
schemaUsage = "A valid JSON schema file to use for validation. Default: schema.json"
fileUsage = "A Yaml or JSON file to check against a given schema. Default: values.json (can acceptable multiples)"
versionUsage = "Prints out the version of schemacheck"
ignoreValidationErrUsage = "Ignores when a document is not valid but provides a warning."
noColorUsage = "Disables color usage for the logger"
)
// Core variables for flag pointers and info, warning, and error loggers.
var (
// Core flag variables
File []string
Schema string
IgnoreValidationErr bool
NoColor bool
VersionFlag bool
// version is set through ldflags by GoReleaser upon build, taking in the most recent tag
// and appending -snapshot in the event that --snapshot is set in GoReleaser.
version string
// Info, warning, and error loggers.
logger = log.New(os.Stderr, "INFO: ", log.Lshortfile)
warnLogger = log.New(os.Stderr, color.HiYellowString("WARN: "), log.Lshortfile)
errLogger = log.New(os.Stderr, color.HiRedString("ERROR: "), log.Lshortfile)
)
// Initialize the flags from the command line and their shorthand counterparts.
func init() {
flag.StringVarP(&Schema, "schema", "s", "", schemaUsage)
flag.StringSliceVarP(&File, "file", "f", []string{}, fileUsage)
flag.BoolVar(&IgnoreValidationErr, "ignore-val-err", false, ignoreValidationErrUsage)
flag.BoolVar(&NoColor, "no-color", false, noColorUsage)
flag.BoolVarP(&VersionFlag, "version", "v", false, versionUsage)
}
// Check whether or not a required flag like file and schema is set and return true or false.
func CheckForEmptyArg() bool {
schemaArgEmpty := true
fileArgEmpty := true
flag.VisitAll(func(f *flag.Flag) {
if f.Name == "schema" {
if f.Changed {
schemaArgEmpty = false
}
} else if f.Name == "file" {
if f.Changed {
fileArgEmpty = false
}
}
})
if schemaArgEmpty || fileArgEmpty {
return true
}
return false
}
// Checks whether a given file is of the supported extension type and if not
// returns false with an error.
// Valid file extensions are currently .yaml, .yml, and .json
func CheckFileIsSupported(file string, fileExt string) (bool, error) {
// default to false
fileValid := false
// supported file extensions to check
supportedTypes := []string{"yaml", "yml", "json"}
for _, ext := range supportedTypes {
if strings.HasSuffix(file, ext) {
logger.Printf("File: \"%s\" has valid file extension: \"%s\"", file, ext)
fileValid = true
}
}
if !fileValid {
return fileValid, errors.New("file type not supported")
}
return fileValid, nil
}
func GetFileExt(file string) (string, error) {
_, fileExt, found := strings.Cut(file, ".")
if !found {
return "", errors.New("file separator not found")
}
return fileExt, nil
}
func Validate(file string, fileExt string, loadedSchema gojsonschema.JSONLoader) error {
data, err := os.ReadFile(filepath.Clean(file))
if err != nil {
errLogger.Fatalf("Could not read file: '%s' cleanly.", file)
}
if fileExt == "yaml" || fileExt == "yml" {
data, err = yaml.YAMLToJSON(data)
if err != nil {
logger.Fatalf("Failed to convert yaml to json in yaml file %s", file)
}
}
documentLoader := gojsonschema.NewBytesLoader(data)
// Validate the JSON data against the loaded JSON Schema
result, err := gojsonschema.Validate(loadedSchema, documentLoader)
if err != nil {
errLogger.Printf("There was a problem validating %s", file)
logger.Fatalf(err.Error())
}
// Check the validity of the result and throw a message is the document is valid or if it's not with errors.
if result.Valid() {
logger.Printf("%s is a valid document.\n", file)
} else {
logger.Printf("%s is not a valid document...\n", file)
for _, desc := range result.Errors() {
errLogger.Printf("--- %s\n", desc)
}
return errors.New("document not valid")
}
return nil
}
func main() {
// parse the flags set in the init() function
flag.Parse()
// If version flag is set, output version of app and exit
if VersionFlag {
fmt.Printf("schemacheck version: %s\n", version)
os.Exit(0)
}
// set first to false for CI based on fatih/color docs
color.NoColor = false
// set nocolor to true, and reset err and warn loggers because nocolor is not respected by logger definitions when set
// at var level
if NoColor {
color.NoColor = true
warnLogger = log.New(os.Stderr, "WARN: ", log.Lshortfile)
errLogger = log.New(os.Stderr, "ERROR: ", log.Lshortfile)
}
// Check to ensure required flags aren't empty
missingArgs := CheckForEmptyArg()
if missingArgs {
fmt.Fprintf(os.Stderr, "Usage of schemacheck\n")
flag.PrintDefaults()
errLogger.Fatal("One or more missing args not set.")
}
// Load schema file before running through and validating the other files to
// reduce how many times it's loaded.
schema, err := os.ReadFile(filepath.Clean(Schema))
if err != nil {
errLogger.Fatalf("Could not read schema file: '%s' cleanly.", Schema)
}
loadedSchema := gojsonschema.NewBytesLoader(schema)
// Iterate through the files declared in the arguments and run validations
for _, file := range File {
// Create a specific logger with an ERROR message for easy readability.
// Print out the values passed on the command line
logger.Printf("Validating %s file against %s schema...", file, Schema)
// Get the file extension and error if it failed
fileExt, err := GetFileExt(file)
if err != nil {
errLogger.Fatalf(err.Error())
}
// Pass the file name and extension to ensure it's a supported file type
if _, err := CheckFileIsSupported(file, fileExt); err != nil {
errLogger.Fatal(err.Error())
}
// Validate against the schema and if IgnoreValidationErr is set, exit with a warning.
if err := Validate(file, fileExt, loadedSchema); err != nil {
if IgnoreValidationErr {
warnLogger.Printf("Ignoring validation error.")
os.Exit(0)
}
errLogger.Fatal(err.Error())
}
}
}