-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathYamlValidator.groovy
More file actions
179 lines (156 loc) · 5.83 KB
/
YamlValidator.groovy
File metadata and controls
179 lines (156 loc) · 5.83 KB
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
@Grab(group = 'org.yaml', module = 'snakeyaml', version = '2.3')
@Grab(group = 'net.sourceforge.argparse4j', module = 'argparse4j', version = '0.9.0')
import java.util.logging.Level
import java.util.logging.Logger
import groovy.transform.Field
import org.yaml.snakeyaml.LoaderOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.AbstractConstruct
import org.yaml.snakeyaml.constructor.Construct
import org.yaml.snakeyaml.constructor.Constructor
import org.yaml.snakeyaml.error.YAMLException
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.NodeId
import org.yaml.snakeyaml.nodes.Tag
import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.inf.ArgumentParser
import net.sourceforge.argparse4j.inf.ArgumentParserException
System.setProperty('java.util.logging.SimpleFormatter.format', '%5$s%6$s%n')
@Field static final Logger LOGGER = Logger.getLogger('YamlValidator.log')
@Field boolean debug
@Field boolean shouldContinue
@Field boolean allowDuplicateKeys
@Field boolean ignoreUnknownTags
ArgumentParser parser = ArgumentParsers.newFor('YamlValidator').build()
.defaultHelp(true)
parser.addArgument('-d', '--debug')
.choices(true, false).setDefault(false)
.type(Boolean)
.help('Flag to turn on debug logging')
parser.addArgument('-c', '--continue')
.choices(true, false).setDefault(true)
.type(Boolean)
.help('Flag to indicate if processing should be continued on error')
parser.addArgument('-p', '--path')
.setDefault(System.getProperty('user.dir'))
.type(String)
.help('Path in which to look for yaml files')
parser.addArgument('-ad', '--allow-duplicate-keys')
.choices(true, false).setDefault(false)
.type(Boolean)
.help('Flag to indicate if YAML files with duplicate keys should be considered valid')
parser.addArgument('-iu', '--ignore-unknown-tags')
.choices(true, false).setDefault(true)
.type(Boolean)
.help('Flag to indicate if YAML files with unknown tags should be considered valid')
final String[] ARGS = getProperty('args') as String[]
def options
try {
options = parser.parseArgs(ARGS)
} catch (ArgumentParserException e) {
parser.handleError(e)
exitWithError()
}
options = parser.parseArgs(ARGS)
debug = options.getBoolean('debug')
shouldContinue = options.getBoolean('continue')
allowDuplicateKeys = options.getBoolean('allow_duplicate_keys')
ignoreUnknownTags = options.getBoolean('ignore_unknown_tags')
if (debug) {
Logger root = Logger.getLogger('')
root.setLevel(Level.FINE)
for (def handler : root.handlers) {
handler.setLevel(Level.FINE)
}
}
boolean isError = validateYamlFiles(new File(options.getString('path')))
if (isError) {
exitWithError()
}
/**
* Validates all yaml files in the provided directory recursively
*
* @param directory
* @return
*/
boolean validateYamlFiles(File directory) {
LOGGER.fine("Validating files in '${directory}'")
LoaderOptions loaderOptions = new LoaderOptions(
allowDuplicateKeys: allowDuplicateKeys
)
Yaml yaml
if (ignoreUnknownTags) {
Constructor constructor = new TagIgnoringConstructor(loaderOptions)
yaml = new Yaml(constructor)
} else {
yaml = new Yaml(loaderOptions)
}
String fileName
boolean isError = false
for (def file : directory.listFiles()) {
LOGGER.fine("File or directory: ${file.absolutePath}")
// Recursively evaluate yaml files in each folder
if (file.directory) {
// To retain overall error status. isError has to be on the right hand side
isError = validateYamlFiles(file) || isError
} else if (file.file) {
fileName = file.absolutePath
LOGGER.fine("File: ${file.absolutePath}")
if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
LOGGER.fine("Validating '$fileName'.")
int index = 1
file.withInputStream { yamlFileInputStream ->
try {
for (def document : yaml.loadAll(yamlFileInputStream)) {
LOGGER.fine("Document $index of '$fileName' is valid")
index++
}
LOGGER.info("'$fileName' is valid")
}
catch (YAMLException e) {
LOGGER.log(Level.SEVERE, "'${fileName}' is invalid. Error: ${e.message}")
if (shouldContinue) {
isError = true
} else {
exitWithError()
}
}
}
}
}
}
return isError
}
/**
* Exits the script because invalid yaml files have been encountered
*/
void exitWithError() {
System.exit(1)
}
/** This code is under copyright (full attribution in NOTICE) and is from:
*
* https://bitbucket.org/snakeyaml/snakeyaml/src/master/src/test/java/examples/IgnoreTagsExampleTest.java
*/
/**
* A snakeyaml Constructor that ignores tags
*/
class TagIgnoringConstructor extends Constructor {
TagIgnoringConstructor(LoaderOptions loaderOptions) {
super(loaderOptions)
this.yamlConstructors[null] = new TagIgnoringConstruct(this.yamlConstructors)
}
}
class TagIgnoringConstruct extends AbstractConstruct {
Map<NodeId, Tag> dataTypesToTagMapping = [
(NodeId.scalar) : Tag.STR,
(NodeId.sequence): Tag.SEQ,
(NodeId.mapping) : Tag.MAP
]
Map<Tag, Construct> yamlConstructors
TagIgnoringConstruct(Map<Tag, Construct> yamlConstructors) {
this.yamlConstructors = yamlConstructors
}
Object construct(Node node) {
return yamlConstructors[dataTypesToTagMapping[node.getNodeId()]].construct(node)
}
}