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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Crossplane
2
+ VERSION = '0.1.5'
3
+ 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: []