crossplane 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/crossplane +7 -0
- data/lib/crossplane/analyzer.rb +1964 -0
- data/lib/crossplane/builder.rb +203 -0
- data/lib/crossplane/cli.rb +139 -0
- data/lib/crossplane/config.rb +56 -0
- data/lib/crossplane/errors.rb +64 -0
- data/lib/crossplane/globals.rb +42 -0
- data/lib/crossplane/lexer.rb +155 -0
- data/lib/crossplane/parser.rb +270 -0
- data/lib/crossplane/utils.rb +128 -0
- data/lib/crossplane/version.rb +3 -0
- metadata +111 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'crossplane/analyzer'
|
2
|
+
require 'crossplane/globals'
|
3
|
+
require 'crossplane/lexer'
|
4
|
+
require 'pathname'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
#require_relative 'analyzer.rb'
|
8
|
+
#require_relative 'globals.rb'
|
9
|
+
#require_relative 'lexer.rb'
|
10
|
+
|
11
|
+
module CrossPlane
|
12
|
+
class Parser
|
13
|
+
attr_accessor :analyzer
|
14
|
+
attr_accessor :catch_errors
|
15
|
+
attr_accessor :combine
|
16
|
+
attr_accessor :comments
|
17
|
+
attr_accessor :filename
|
18
|
+
attr_accessor :ignore
|
19
|
+
attr_accessor :included
|
20
|
+
attr_accessor :includes
|
21
|
+
attr_accessor :lexer
|
22
|
+
attr_accessor :payload
|
23
|
+
attr_accessor :single
|
24
|
+
attr_accessor :strict
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
args = args[0] || {}
|
28
|
+
|
29
|
+
required = ['filename']
|
30
|
+
conflicts = []
|
31
|
+
requires = {}
|
32
|
+
valid = {
|
33
|
+
'params' => [
|
34
|
+
'catch_errors',
|
35
|
+
'combine',
|
36
|
+
'comments',
|
37
|
+
'filename',
|
38
|
+
'ignore',
|
39
|
+
'single',
|
40
|
+
'strict',
|
41
|
+
]
|
42
|
+
}
|
43
|
+
|
44
|
+
content = CrossPlane.utils.validate_constructor(client: self, args: args, required: required, conflicts: conflicts, requires: requires, valid: valid)
|
45
|
+
self.catch_errors = (content[:catch_errors] && content[:catch_errors] == true) ? true : false
|
46
|
+
self.combine = (content[:combine] && content[:combine] == true) ? true : false
|
47
|
+
self.comments = (content[:comments] && content[:comments] == true) ? true : false
|
48
|
+
self.filename = content[:filename]
|
49
|
+
self.ignore = content[:ignore] ? content[:ignore] : []
|
50
|
+
self.single = (content[:single] && content[:single] == true) ? true : false
|
51
|
+
self.strict = (content[:strict] && content[:strict] == true) ? true : false
|
52
|
+
|
53
|
+
self.analyzer = CrossPlane::Analyzer.new()
|
54
|
+
self.lexer = CrossPlane::Lexer.new(filename: self.filename)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse(*args, onerror:nil)
|
58
|
+
config_dir = File.dirname(self.filename)
|
59
|
+
|
60
|
+
self.payload = {
|
61
|
+
'status' => 'ok',
|
62
|
+
'errors' => [],
|
63
|
+
'config' => [],
|
64
|
+
}
|
65
|
+
|
66
|
+
self.includes = [[self.filename, []]] # stores (filename, config context) tuples
|
67
|
+
self.included = {self.filename => 0} # stores {filename: array index} hash
|
68
|
+
|
69
|
+
def _prepare_if_args(stmt)
|
70
|
+
args = stmt['args']
|
71
|
+
if args and args[0].start_with?('(') and args[-1].end_with?(')')
|
72
|
+
args[0] = args[0][1..-1] # left strip this
|
73
|
+
args[-1] = args[-1][0..-2].rstrip
|
74
|
+
s = args[0].empty? ? 1 : 0
|
75
|
+
e = args.length - (args[-1].empty? ? 1 : 0)
|
76
|
+
args = args[s..e]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def _handle_error(parsing, e, onerror: nil)
|
81
|
+
file = parsing['file']
|
82
|
+
error = e.to_s
|
83
|
+
line = e.respond_to?('lineno') ? e.lineno : nil
|
84
|
+
|
85
|
+
parsing_error = {'error' => error, 'line' => line}
|
86
|
+
payload_error = {'file' => file, 'error' => error, 'line' => line}
|
87
|
+
if not onerror.nil? and not onerror.empty?
|
88
|
+
payload_error['callback'] = onerror(e)
|
89
|
+
end
|
90
|
+
|
91
|
+
parsing['status'] = 'failed'
|
92
|
+
parsing['errors'].push(parsing_error)
|
93
|
+
|
94
|
+
self.payload['status'] = 'failed'
|
95
|
+
self.payload['errors'].push(payload_error)
|
96
|
+
end
|
97
|
+
|
98
|
+
def _parse(parsing, tokens, ctx:[], consume: false)
|
99
|
+
fname = parsing['file']
|
100
|
+
parsed = []
|
101
|
+
|
102
|
+
begin
|
103
|
+
while tuple = tokens.next
|
104
|
+
token, lineno = tuple
|
105
|
+
# we are parsing a block, so break if it's closing
|
106
|
+
break if token == '}'
|
107
|
+
|
108
|
+
if consume == true
|
109
|
+
if token == '{'
|
110
|
+
_parse(parsing, tokens, consume: true)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
directive = token
|
115
|
+
|
116
|
+
if self.combine
|
117
|
+
stmt = {
|
118
|
+
'file' => fname,
|
119
|
+
'directive' => directive,
|
120
|
+
'line' => lineno,
|
121
|
+
'args' => [],
|
122
|
+
}
|
123
|
+
else
|
124
|
+
stmt = {
|
125
|
+
'directive' => directive,
|
126
|
+
'line' => lineno,
|
127
|
+
'args' => [],
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
# if token is comment
|
132
|
+
if directive.start_with?('#')
|
133
|
+
if self.comments
|
134
|
+
stmt['directive'] = '#'
|
135
|
+
stmt['comment'] = token[1..-1].lstrip
|
136
|
+
parsed.push(stmt)
|
137
|
+
end
|
138
|
+
next
|
139
|
+
end
|
140
|
+
|
141
|
+
# TODO: add external parser checking and handling
|
142
|
+
|
143
|
+
# parse arguments by reading tokens
|
144
|
+
args = stmt['args']
|
145
|
+
token, _ = tokens.next
|
146
|
+
while not ['{', '}', ';'].include?(token)
|
147
|
+
stmt['args'].push(token)
|
148
|
+
token, _ = tokens.next
|
149
|
+
end
|
150
|
+
|
151
|
+
# consume the directive if it is ignored and move on
|
152
|
+
if self.ignore.include?(stmt['directive'])
|
153
|
+
# if this directive was a block consume it too
|
154
|
+
if token == '{'
|
155
|
+
_parse(parsing, tokens, consume: true)
|
156
|
+
end
|
157
|
+
next
|
158
|
+
end
|
159
|
+
|
160
|
+
# prepare arguments
|
161
|
+
if stmt['directive'] == 'if'
|
162
|
+
_prepare_if_args(stmt)
|
163
|
+
end
|
164
|
+
|
165
|
+
begin
|
166
|
+
# raise errors if this statement is invalid
|
167
|
+
self.analyzer.analyze(
|
168
|
+
fname,
|
169
|
+
stmt,
|
170
|
+
token,
|
171
|
+
ctx,
|
172
|
+
self.strict
|
173
|
+
)
|
174
|
+
rescue NgxParserDirectiveError => e
|
175
|
+
if self.catch_errors
|
176
|
+
_handle_error(parsing, e)
|
177
|
+
|
178
|
+
# if it was a block but shouldn't have been then consume
|
179
|
+
if e.strerror.end_with(' is not terminated by ";"')
|
180
|
+
if token != '}'
|
181
|
+
_parse(parsing, tokens, consume: true)
|
182
|
+
else
|
183
|
+
break
|
184
|
+
end
|
185
|
+
end
|
186
|
+
next
|
187
|
+
else
|
188
|
+
raise e
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# add "includes" to the payload if this is an include statement
|
193
|
+
if not self.single and stmt['directive'] == 'include'
|
194
|
+
pattern = args[0]
|
195
|
+
p = Pathname.new(args[0])
|
196
|
+
if not p.absolute?
|
197
|
+
pattern = File.join(config_dir, args[0])
|
198
|
+
end
|
199
|
+
|
200
|
+
stmt['includes'] = []
|
201
|
+
|
202
|
+
# get names of all included files
|
203
|
+
# ruby needs a python glob.has_magic equivalent
|
204
|
+
if pattern =~ /\*/
|
205
|
+
fnames = Dir.glob(pattern)
|
206
|
+
else
|
207
|
+
begin
|
208
|
+
open(pattern).close
|
209
|
+
fnames = [pattern]
|
210
|
+
rescue Exception => e
|
211
|
+
f = CrossPlane::NgxParserIncludeError.new(fname, stmt['line'], e.message)
|
212
|
+
fnames = []
|
213
|
+
if self.catch_errors
|
214
|
+
_handle_error(parsing, f)
|
215
|
+
else
|
216
|
+
raise f
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
fnames.each do |fname|
|
222
|
+
# the included set keeps files from being parsed twice
|
223
|
+
# TODO: handle files included from multiple contexts
|
224
|
+
if not self.included.include?(fname)
|
225
|
+
self.included[fname] = self.includes.length
|
226
|
+
self.includes.push([fname, ctx])
|
227
|
+
end
|
228
|
+
index = self.included[fname]
|
229
|
+
stmt['includes'].push(index)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# if this statement terminated with '{' then it is a block
|
234
|
+
if token == '{'
|
235
|
+
inner = self.analyzer.enter_block_ctx(stmt, ctx) # get context for block
|
236
|
+
stmt['block'] = _parse(parsing, tokens, ctx: inner)
|
237
|
+
end
|
238
|
+
parsed.push(stmt)
|
239
|
+
end
|
240
|
+
return parsed
|
241
|
+
rescue StopIteration
|
242
|
+
return parsed
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
self.includes.each do |fname, ctx|
|
247
|
+
tokens = self.lexer.lex().to_enum
|
248
|
+
parsing = {
|
249
|
+
'file' => fname,
|
250
|
+
'status' => 'ok',
|
251
|
+
'errors' => [],
|
252
|
+
'parsed' => [],
|
253
|
+
}
|
254
|
+
|
255
|
+
begin
|
256
|
+
parsing['parsed'] = _parse(parsing, tokens.to_enum, ctx: ctx)
|
257
|
+
rescue Exception => e
|
258
|
+
_handle_error(parsing, e, onerror: onerror)
|
259
|
+
end
|
260
|
+
self.payload['config'].push(parsing)
|
261
|
+
end
|
262
|
+
|
263
|
+
if self.combine
|
264
|
+
return _combine_parsed_configs(payload)
|
265
|
+
else
|
266
|
+
return self.payload
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'crossplane/errors'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
#require_relative 'errors.rb'
|
5
|
+
|
6
|
+
module CrossPlane
|
7
|
+
class Utils
|
8
|
+
attr_accessor :logger
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
self.logger = configure_logger()
|
12
|
+
end
|
13
|
+
|
14
|
+
def isspace(string)
|
15
|
+
return string =~ /^\s+$/ ? true : false
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_random(length: 64)
|
19
|
+
o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
|
20
|
+
string = (0...length).map { o[rand(o.length)] }.join
|
21
|
+
string
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure_logger(debug: nil)
|
25
|
+
logger = Logger.new(STDOUT)
|
26
|
+
logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
27
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
28
|
+
format("[%s] %s\n", severity.capitalize, msg)
|
29
|
+
end
|
30
|
+
return logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_constructor(client: nil, args: nil, required: nil, conflicts: nil, requires: nil, valid: {})
|
34
|
+
content = {}
|
35
|
+
errors = []
|
36
|
+
booleans = ['catch_errors', 'combine', 'comments', 'single', 'strict']
|
37
|
+
hashes = []
|
38
|
+
arrays = ['ignore']
|
39
|
+
ints = ['indent']
|
40
|
+
args = Hash[args.map{ |k, v| [k.to_s, v] }]
|
41
|
+
args.each do |k, v|
|
42
|
+
args.delete(k) if v == nil
|
43
|
+
end
|
44
|
+
|
45
|
+
if ((required - args.keys).length != 0)
|
46
|
+
errors.push(missing_opts_error(required - args.keys))
|
47
|
+
end
|
48
|
+
|
49
|
+
if conflicts.length > 0
|
50
|
+
conflicts.each do |conflict_item|
|
51
|
+
if (0 && (conflict_item & args.keys).length > 1)
|
52
|
+
errors.push(conflicting_opts_error(conflict_item))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if requires.keys.length > 0
|
58
|
+
requires.each do |key, required|
|
59
|
+
if args.keys.include?(key)
|
60
|
+
intersection = required & args.keys
|
61
|
+
unless (required & args.keys).length == required.length
|
62
|
+
missing = required - intersection
|
63
|
+
if missing.length > 0
|
64
|
+
errors.push(missing_requires_error(key, missing))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if valid['params']
|
72
|
+
valid['params'].each do |param|
|
73
|
+
if (args.has_key?(param)) and (valid['params'].include?(param))
|
74
|
+
if ints.include?(param)
|
75
|
+
begin
|
76
|
+
content[param.to_sym] = args[param].to_i
|
77
|
+
rescue
|
78
|
+
errors.push(format('%s must be an integer', param))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if booleans.include?(param)
|
83
|
+
if args[param].is_a?(TrueClass) or args[param].is_a?(FalseClass)
|
84
|
+
content[param.to_sym] = args[param] == true ? true : false
|
85
|
+
elsif args[param].is_a?(String)
|
86
|
+
content[param.to_sym] = args[param].downcase == 'true' ? true : false
|
87
|
+
else
|
88
|
+
content[param.to_sym] = false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
content[param.to_sym] = args[param]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
if errors.length > 0
|
97
|
+
raise CrossPlane::ConstructorError.new(errors: errors)
|
98
|
+
else
|
99
|
+
return content
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def conflicting_opts_error(conflict)
|
105
|
+
return format('the following parameters are mutually exclusive: %s', conflict.join(', '))
|
106
|
+
end
|
107
|
+
|
108
|
+
def missing_requires_error(key, missing)
|
109
|
+
return format('if you specify "%s", you must also specify: (%s)', key, missing.join(', '))
|
110
|
+
end
|
111
|
+
|
112
|
+
def missing_opts_error(missing)
|
113
|
+
return format('the following required parameters are missing: %s', missing.join(', '))
|
114
|
+
end
|
115
|
+
|
116
|
+
def disallowed_opts_error(disallowed)
|
117
|
+
return format('the following disallowed parameters were specified in the constructor: %s', disallowed.join(', '))
|
118
|
+
end
|
119
|
+
|
120
|
+
def missing_optional_error(oneof)
|
121
|
+
return format('the constructor requires one of the following parameters: %s', oneof.join(', '))
|
122
|
+
end
|
123
|
+
|
124
|
+
def too_many_optional_error(oneof)
|
125
|
+
return format('the constructor will accept only one of the following parameters: %s', oneof.join(', '))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crossplane
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gary Danko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.2.3
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.2.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '10.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '10.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: thor
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.20'
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.20.0
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.20'
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.20.0
|
67
|
+
description: Quick and reliable way to convert NGINX configurations into JSON and
|
68
|
+
back. Based on the NGINX crossplane project at https://github.com/nginxinc/crossplane
|
69
|
+
email:
|
70
|
+
- gary_danko@intuit.com
|
71
|
+
executables:
|
72
|
+
- crossplane
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- bin/crossplane
|
77
|
+
- lib/crossplane/analyzer.rb
|
78
|
+
- lib/crossplane/builder.rb
|
79
|
+
- lib/crossplane/cli.rb
|
80
|
+
- lib/crossplane/config.rb
|
81
|
+
- lib/crossplane/errors.rb
|
82
|
+
- lib/crossplane/globals.rb
|
83
|
+
- lib/crossplane/lexer.rb
|
84
|
+
- lib/crossplane/parser.rb
|
85
|
+
- lib/crossplane/utils.rb
|
86
|
+
- lib/crossplane/version.rb
|
87
|
+
homepage: https://github.com/gdanko/crossplane
|
88
|
+
licenses:
|
89
|
+
- GPL-2.0
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 2.2.3
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.4.5.1
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Reliable and fast NGINX configuration file parser and builder
|
111
|
+
test_files: []
|