sapluuna 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f176e218e8e443cec9eaa8e94f6ca233ae9f8014
4
+ data.tar.gz: 3f783c2bc0f48f9297a052bd0d8b55352b4a2833
5
+ SHA512:
6
+ metadata.gz: f174827d07bc20c043cb03f6c792518dc2b7ef42d2bb86aa6b53b521792aa57a892f3e7d38263b8a5ba2756c006eea10e24f0bbf518e90a12eaff416136f1b78
7
+ data.tar.gz: 364fc6803738df805c191364e8c8a9eecb1693e8b3159b003fda9aba93d8a908006247b3289d9bd746eff16d5883dd588bb0203a56c4876fa6aa50ffadd93415
@@ -0,0 +1,2 @@
1
+ gems
2
+ cfg*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,11 @@
1
+ # Sapluuna
2
+
3
+ Silly little template based configuration maker
4
+
5
+ * Anything outside {{{ ... }}} is comment and won't be in generated file
6
+ * Inside {{{ }}} you can have <% foo %> which is just ruby
7
+ * For method_missing in <% foo %> we try variable[name] hash, given to constrcutor, i.e. Sapluuna.new variables: {replace_this: 'with_this'} .... <% replace_this %> works
8
+ * {{{ can be followed by negative or positive labels, if labels match to those given to constructor {{{ }}} is evaluated, otherwise ignored
9
+ * rationale for labels is {{{ PE ..... }}} or {{{ Finland Sweden ..... }}} to conditionally evaluate blocks
10
+ * You can query the instance on what variables are needed when labels X are set, use-case is in say in webUI to automatically generate form with all variables template needs
11
+
@@ -0,0 +1,47 @@
1
+ begin
2
+ require 'rake/testtask'
3
+ require 'bundler'
4
+ # Bundler.setup
5
+ rescue LoadError
6
+ warn 'bundler missing'
7
+ end
8
+
9
+ gemspec = eval(File.read(Dir['*.gemspec'].first))
10
+ file = [gemspec.name, gemspec.version].join('-') + '.gem'
11
+
12
+ desc 'Validate gemspec'
13
+ task :gemspec do
14
+ gemspec.validate
15
+ end
16
+
17
+ desc 'Run minitest'
18
+ task :test do
19
+ Rake::TestTask.new do |t|
20
+ t.libs.push "lib"
21
+ t.test_files = FileList['spec/*_spec.rb']
22
+ t.verbose = true
23
+ end
24
+ end
25
+
26
+ desc 'Build gem'
27
+ task :build do
28
+ system "gem build #{gemspec.name}.gemspec"
29
+ FileUtils.mkdir_p 'gems'
30
+ FileUtils.mv file, 'gems'
31
+ end
32
+
33
+ desc 'Install gem'
34
+ task :install => :build do
35
+ system "sudo -Es sh -c \'umask 022; gem install gems/#{file}\'"
36
+ end
37
+
38
+ desc 'Remove gems'
39
+ task :clean do
40
+ FileUtils.rm_rf 'gems'
41
+ end
42
+
43
+ desc 'Push to rubygems'
44
+ task :push do
45
+ system "gem push gems/#{file}"
46
+ system "git tag #{gemspec.version}"
47
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require_relative '../lib/sapluuna/cli'
5
+ k = Sapluuna::CLI.new
6
+ @debug = k.debug
7
+ k.run
8
+ rescue => error
9
+ warn "ERROR: #{error.message} (#{error.class})"
10
+ raise if @debug
11
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'ip'
2
+
3
+ class Sapluuna
4
+ module ClassHelpers
5
+
6
+ class ::String
7
+ def ip
8
+ IP.new(self).ip
9
+ end
10
+ def acl
11
+ IP.new(self).acl
12
+ end
13
+ def net
14
+ IP.new(self).net
15
+ end
16
+ def cidr
17
+ IP.new(self).cidr
18
+ end
19
+ end
20
+
21
+ class ::Array
22
+ def as_ip format_string
23
+ as_method 'ip', format_string
24
+ end
25
+
26
+ def as_acl format_string
27
+ as_method 'acl', format_string
28
+ end
29
+
30
+ def as_cidr format_string
31
+ as_method 'cidr', format_string
32
+ end
33
+
34
+ private
35
+
36
+ def as_method method, format_string
37
+ map do |str|
38
+ format_string % str.send(method)
39
+ end.join("\n")
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,61 @@
1
+ require_relative 'sapluuna'
2
+ begin
3
+ require 'slop'
4
+ rescue LoadError
5
+ warn "sudo gem install slop ## required by sapluuna CLI"
6
+ exit 42
7
+ end
8
+
9
+
10
+
11
+ class Sapluuna
12
+ class CLI
13
+ ROOT = '.'
14
+ attr_reader :debug
15
+
16
+ def initialize
17
+ @opts = opts_parse
18
+ args = @opts.arguments
19
+ @file = args.shift
20
+ @labels = @opts[:label].split(/[,\s]+/) if @opts[:label]
21
+ @vars = {}
22
+ @disco = @opts[:variables]
23
+ args.each do |var|
24
+ name, value = var.split '='
25
+ @vars[name.to_sym] = value
26
+ end
27
+ if @opts.debug?
28
+ @debug = true
29
+ Log.level = Logger::DEBUG
30
+ end
31
+ end
32
+
33
+ def run
34
+ raise MissingOption, 'File is mandatory argument' unless @file
35
+ sap = Sapluuna.new labels: @labels, variables: @vars,
36
+ discover_variables: @disco,
37
+ root_directory: (@opts[:root] or ROOT)
38
+ cfg = sap.parse File.read(@file)
39
+ puts @disco ? sap.discovered_variables.keys : cfg
40
+ rescue => error
41
+ crash error
42
+ raise
43
+ end
44
+
45
+ private
46
+
47
+ def opts_parse
48
+ Slop.parse do |o|
49
+ o.banner = 'Usage: sapluuna [OPTIONS] FILE [variables]'
50
+ o.bool '-d', '--debug', 'turn on debugging'
51
+ o.string '-l', '--label', 'commma separated list of labels'
52
+ o.bool '-v', '--variables', 'displays required variables'
53
+ o.string '-r', '--root', 'root directory for template import'
54
+ o.on '-h', '--help' do puts o; exit; end
55
+ end
56
+ end
57
+
58
+ def crash error
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,94 @@
1
+ class Sapluuna
2
+ class Context
3
+ attr_reader :discovered_variables, :variables
4
+
5
+ RootDirectory = '.'
6
+
7
+ class VariableMissing < Error; end
8
+
9
+ def initialize opts
10
+ @opts = opts.dup
11
+ @discover_variables = opts.delete :discover_variables
12
+ @variables = (opts.delete(:variables) or {})
13
+ @root_directory = (opts.delete(:root_directory) or RootDirectory)
14
+ @output = ''
15
+ @discovered_variables = {}
16
+ end
17
+
18
+ def cfg value
19
+ @output << value
20
+ end
21
+
22
+ def code value
23
+ @output << eval(value).to_s
24
+ end
25
+
26
+ def str
27
+ @output
28
+ end
29
+
30
+ def are value
31
+ [:are, value]
32
+ end
33
+
34
+ def is value
35
+ [:is, value]
36
+ end
37
+
38
+ def silent *args
39
+ ""
40
+ end
41
+
42
+ private
43
+
44
+ def import file
45
+ template = File.read resolve_file(file)
46
+ @opts[:variables] = @variables
47
+ @opts[:root_directory] = @root_directory.dup
48
+ Log.debug "importing #{file}"
49
+ sapl = Sapluuna.new @opts
50
+ output = sapl.parse template
51
+ @discovered_variables.merge! sapl.discovered_variables
52
+ add_indent output.lines, @output.lines.last
53
+ rescue => error
54
+ raise error, "#{error.message} (while reading #{file})"
55
+ end
56
+
57
+ def resolve_file file
58
+ # should we avoid ../../../etc/passwd style input here?
59
+ # why? we control templates?
60
+ File.join @root_directory, file
61
+ end
62
+
63
+ def add_indent output, indent_hint
64
+ return output.join.chomp if output.size < 2 or not indent_hint
65
+ indent_size = indent_hint.match(/\A\s*/)[0].size
66
+ first_line = output[0]
67
+ output = output[1..-1].map { |line| ' ' * indent_size + line }
68
+ output.unshift(first_line).join.chomp
69
+ end
70
+
71
+ def method_missing method, *args
72
+ if Array === args.first
73
+ value = args.first.last
74
+ case args.first.first
75
+ when :is
76
+ @variables[method] = value
77
+ when :are
78
+ @variables[method] = value.to_s.strip.split(/\s+/)
79
+ end
80
+ ""
81
+ elsif @variables[method]
82
+ @variables[method]
83
+ else
84
+ if @discover_variables
85
+ args ||= ['']
86
+ @discovered_variables[method] = args[0] unless @discovered_variables.has_key? method
87
+ ""
88
+ else
89
+ raise VariableMissing, "variable '#{method}' required, but not given"
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,51 @@
1
+ require 'ipaddr'
2
+
3
+ class Sapluuna
4
+ class IP < IPAddr
5
+
6
+ def initialize(addr = '::', family = Socket::AF_UNSPEC)
7
+ addr_org, _family_org = addr, family
8
+ prefix, _prefixlen = addr_org.split('/')
9
+ @addr_org = prefix
10
+ super
11
+ if @family == Socket::AF_UNSPEC or @family == Socket::AF_INET
12
+ @addr_org = in_addr(@addr_org)
13
+ else
14
+ @addr_org = in6_addr(@addr_org)
15
+ end
16
+ end
17
+
18
+ def ip
19
+ addr_tmp = @addr
20
+ @addr = @addr_org
21
+ ip = self.to_s
22
+ @addr = addr_tmp
23
+ ip
24
+ end
25
+
26
+ def mask_cidr
27
+ @mask_addr.to_s(2).delete('0').size
28
+ end
29
+
30
+ def mask_wild
31
+ _to_string ~@mask_addr
32
+ end
33
+
34
+ def mask_net
35
+ _to_string @mask_addr
36
+ end
37
+
38
+ def cidr
39
+ ip + '/' + mask_cidr.to_s
40
+ end
41
+
42
+ def acl
43
+ ip + ' '+ mask_wild
44
+ end
45
+
46
+ def net
47
+ ip + ' ' + mask_net
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ require 'strscan'
2
+
3
+ class Sapluuna
4
+ class Parser
5
+ CODE_OPEN = '<%\s*'
6
+ CODE_CLOSE = '\s*%>'
7
+ TEMPLATE_OPEN = '^\s*{{{[\t ]*'
8
+ TEMPLATE_CLOSE = '\s*}}}\s*$'
9
+ NEW_LINE = "\n"
10
+ class ParserError < Error; end
11
+ class UnterminatedBlock < ParserError; end
12
+
13
+ def initialize
14
+ @sc = StringScanner.new ''
15
+ end
16
+
17
+ def parse input
18
+ level = 0
19
+ cfg = []
20
+ @sc.string = input
21
+ loop do
22
+ if @sc.scan_until Regexp.new(TEMPLATE_OPEN)
23
+ cfg << [:template, get_labels, get_template(level)]
24
+ else
25
+ break
26
+ end
27
+ end
28
+ @sc.string = '' # no need to keep it in memory
29
+ cfg
30
+ end
31
+
32
+ private
33
+
34
+ def get_labels
35
+ labels = @sc.scan_until Regexp.new(NEW_LINE)
36
+ labels.strip.split(/\s+/)
37
+ end
38
+
39
+ def re_combine *args
40
+ re = args.map do |arg|
41
+ '(?:%s)' % arg
42
+ end.join('|')
43
+ Regexp.new re
44
+ end
45
+
46
+ def clean_scan scan
47
+ endstr = -(@sc.matched.size+1)
48
+ scan[0..endstr]
49
+ end
50
+
51
+ def get_template level
52
+ cfg = []
53
+ loop do
54
+ scan = @sc.scan_until re_combine(CODE_OPEN, TEMPLATE_OPEN, TEMPLATE_CLOSE)
55
+ raise UnterminatedBlock, "template at #{level}, #{@sc.pos} runs forever" unless scan
56
+ cfg << [:cfg, clean_scan(scan)]
57
+ case @sc.matched
58
+
59
+ when Regexp.new(CODE_OPEN)
60
+ scan = @sc.scan_until Regexp.new(CODE_CLOSE)
61
+ raise UnterminatedBlock, "code at #{level}, #{@sc.pos} runs forever" unless scan
62
+ cfg << [:code, clean_scan(scan)]
63
+
64
+ when Regexp.new(TEMPLATE_OPEN)
65
+ cfg << [:template, get_labels, get_template(level+1)]
66
+
67
+ when Regexp.new(TEMPLATE_CLOSE)
68
+ return cfg
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,91 @@
1
+ require 'logger'
2
+ require_relative 'class_helpers'
3
+
4
+ class Sapluuna
5
+
6
+ include ClassHelpers
7
+ class Error < StandardError; end
8
+ class InvalidOption < StandardError; end
9
+ class MissingOption < StandardError; end
10
+ class InvalidType < Error; end
11
+ Log = Logger.new STDERR
12
+ Log.level = Logger::WARN
13
+
14
+ def initialize opts
15
+ @context = (opts[:context] or Context)
16
+ @want_labels = read_labels (opts[:labels] or [])
17
+ @context = @context.new(opts) if @context.class == Class
18
+ @parser = Parser.new
19
+ end
20
+
21
+ def discovered_variables
22
+ @context.discovered_variables
23
+ end
24
+
25
+ def variables
26
+ @context.variables
27
+ end
28
+
29
+ def parse input
30
+ @parser.parse(input).each do |cfg|
31
+ type = cfg.shift
32
+ case type
33
+ when :template
34
+ template cfg
35
+ else
36
+ raise InvalidType, "#{type} was not recognized by parser"
37
+ end
38
+ end
39
+ @context.str
40
+ end
41
+
42
+ private
43
+
44
+ def read_labels labels
45
+ pos, neg = [], []
46
+ labels.each do |label|
47
+ label[0] == '!' ? neg.push(label[1..-1]) : pos.push(label)
48
+ end
49
+ [pos, neg]
50
+ end
51
+
52
+ def template templ
53
+ return unless valid_labels? read_labels(templ.shift)
54
+ templ.shift.each do |t|
55
+ type = t.shift
56
+ case type
57
+ when :code
58
+ @context.code t.last
59
+ when :cfg
60
+ @context.cfg t.last
61
+ when :template
62
+ template t
63
+ else
64
+ raise InvalidType, "#{type} was not recognized by parser"
65
+ end
66
+ end
67
+ end
68
+
69
+ def valid_labels? got_labels
70
+ # first/[0] is our positive labels (label)
71
+ # last/[1] is our negative labels (!label)
72
+
73
+ # template has label which we explcitly don't want
74
+ return false if (got_labels[0] & @want_labels[1]).size > 0
75
+
76
+ # template forbids label which we explicitly want
77
+ return false if (got_labels[1] & @want_labels[0]).size > 0
78
+
79
+ # template requires no labels
80
+ return true if got_labels[0].empty?
81
+
82
+ # template requires labels, do we want at least one of them?
83
+ return false unless (got_labels[0] & @want_labels[0]).size > 0
84
+
85
+ true
86
+ end
87
+
88
+ end
89
+
90
+ require_relative 'parser'
91
+ require_relative 'context'
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'sapluuna'
3
+ s.version = '0.1.4'
4
+ s.licenses = %w( Apache-2.0 )
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = [ 'Saku Ytti' ]
7
+ s.email = %w( saku@ytti.fi )
8
+ s.homepage = 'http://github.com/ytti/sapluuna'
9
+ s.summary = 'Template parser'
10
+ s.description = 'Template based network configuration generator'
11
+ s.rubyforge_project = s.name
12
+ s.files = `git ls-files`.split("\n")
13
+ s.executables = %w( sapluuna )
14
+ s.require_path = 'lib/sapluuna'
15
+
16
+ s.required_ruby_version = '>= 2.0.0'
17
+ s.add_runtime_dependency 'slop', '~> 4.0'
18
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sapluuna
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Saku Ytti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ description: Template based network configuration generator
28
+ email:
29
+ - saku@ytti.fi
30
+ executables:
31
+ - sapluuna
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - Gemfile
37
+ - README.md
38
+ - Rakefile
39
+ - bin/sapluuna
40
+ - lib/sapluuna/class_helpers.rb
41
+ - lib/sapluuna/cli.rb
42
+ - lib/sapluuna/context.rb
43
+ - lib/sapluuna/ip.rb
44
+ - lib/sapluuna/parser.rb
45
+ - lib/sapluuna/sapluuna.rb
46
+ - sapluuna.gemspec
47
+ homepage: http://github.com/ytti/sapluuna
48
+ licenses:
49
+ - Apache-2.0
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib/sapluuna
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.0.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project: sapluuna
67
+ rubygems_version: 2.2.2
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Template parser
71
+ test_files: []