mantra 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +8 -2
  5. data/README.md +126 -16
  6. data/bin/mantra +17 -0
  7. data/ci/README.md +6 -0
  8. data/ci/docker/Dockerfile +20 -0
  9. data/ci/docker/build-docker-image +10 -0
  10. data/ci/pipeline.yml +42 -0
  11. data/ci/scripts/run-tests +3 -0
  12. data/ci/stub.example.yml +6 -0
  13. data/ci/stub.yml +33 -0
  14. data/ci/tasks/run-tests-dev.yml +10 -0
  15. data/ci/tasks/run-tests-stable.yml +13 -0
  16. data/lib/mantra/certificate.rb +35 -0
  17. data/lib/mantra/certificates/cortege.rb +64 -0
  18. data/lib/mantra/certificates/group.rb +26 -0
  19. data/lib/mantra/command.rb +51 -0
  20. data/lib/mantra/commands/find.rb +38 -0
  21. data/lib/mantra/commands/transform.rb +38 -0
  22. data/lib/mantra/helpers/object_with_type.rb +60 -0
  23. data/lib/mantra/helpers/regexp_helper.rb +12 -0
  24. data/lib/mantra/helpers/template_helper.rb +70 -0
  25. data/lib/mantra/manifest/array_element.rb +84 -0
  26. data/lib/mantra/manifest/element.rb +175 -0
  27. data/lib/mantra/manifest/ext.rb +33 -0
  28. data/lib/mantra/manifest/hash_element.rb +88 -0
  29. data/lib/mantra/manifest/leaf_element.rb +38 -0
  30. data/lib/mantra/manifest/root_element.rb +38 -0
  31. data/lib/mantra/manifest/scope/array_scope.rb +53 -0
  32. data/lib/mantra/manifest/scope/empty_scope.rb +24 -0
  33. data/lib/mantra/manifest/scope/hash_scope.rb +20 -0
  34. data/lib/mantra/manifest/scope/leaf_scope.rb +0 -0
  35. data/lib/mantra/manifest/scope.rb +94 -0
  36. data/lib/mantra/manifest.rb +47 -0
  37. data/lib/mantra/merge_tool/spiff.rb +23 -0
  38. data/lib/mantra/merge_tool.rb +13 -0
  39. data/lib/mantra/transform/extract.rb +10 -0
  40. data/lib/mantra/transform/extract_certificates.rb +18 -0
  41. data/lib/mantra/transform/extract_certificates_to_files.rb +12 -0
  42. data/lib/mantra/transform/extract_section.rb +10 -0
  43. data/lib/mantra/transform/inputs/any.rb +10 -0
  44. data/lib/mantra/transform/inputs/array.rb +16 -0
  45. data/lib/mantra/transform/inputs/file.rb +16 -0
  46. data/lib/mantra/transform/inputs/folder.rb +9 -0
  47. data/lib/mantra/transform/inputs/hash.rb +16 -0
  48. data/lib/mantra/transform/inputs/string.rb +9 -0
  49. data/lib/mantra/transform/merge.rb +26 -0
  50. data/lib/mantra/transform/replace.rb +34 -0
  51. data/lib/mantra/transform/templatize_ip_address.rb +129 -0
  52. data/lib/mantra/transform/templatize_value.rb +63 -0
  53. data/lib/mantra/transform.rb +118 -0
  54. data/lib/mantra/version.rb +1 -1
  55. data/lib/mantra.rb +9 -1
  56. data/mantra.gemspec +6 -5
  57. metadata +69 -8
  58. data/bin/console +0 -14
  59. data/bin/setup +0 -8
@@ -0,0 +1,94 @@
1
+ module Mantra
2
+ class Manifest
3
+ class Scope
4
+
5
+ class ScopeParseError < Exception; end
6
+
7
+ include Helpers::ObjectWithType
8
+ include Helpers::RegexpHelper
9
+
10
+ def self.parse(scope_or_string)
11
+ return scope_or_string if scope_or_string.is_a?(Scope)
12
+ parse_selector(scope_or_string)
13
+ end
14
+
15
+ HASH_SELECTOR_REGEXP = /^([a-zA-Z0-9\_\-\=\*]+)\.?(.*)$/
16
+ ARRAY_SELECTOR_REGEXP = /^\[([a-zA-Z0-9\:\_\-\=\*]*)\]\.?(.*)$/
17
+
18
+ def self.parse_selector(selector)
19
+ case selector
20
+ when HASH_SELECTOR_REGEXP
21
+ head_selector, tail_selector = split_selector(selector, HASH_SELECTOR_REGEXP)
22
+ HashScope.new(scope: head_selector, next: parse_selector(tail_selector))
23
+ when ARRAY_SELECTOR_REGEXP
24
+ head_selector, tail_selector = split_selector(selector, ARRAY_SELECTOR_REGEXP)
25
+ ArrayScope.new(scope: head_selector, next: parse_selector(tail_selector))
26
+ when ""
27
+ EmptyScope.new()
28
+ else
29
+ raise_parse_error(selector)
30
+ end
31
+ end
32
+
33
+ def self.split_selector(selector, matcher_regex)
34
+ matcher = selector.match(matcher_regex)
35
+ raise_parse_error(selector) if matcher.nil?
36
+ head_selector = matcher[1]
37
+ tail_selector = matcher[2]
38
+ return head_selector, tail_selector
39
+ end
40
+
41
+ def self.raise_parse_error(selector)
42
+ raise ScopeParseError.new("Can't parse selector: #{selector}")
43
+ end
44
+
45
+ attr_accessor :scope, :next
46
+
47
+ def initialize(options={})
48
+ self.scope, self.next = options[:scope], options[:next]
49
+ end
50
+
51
+ def last?
52
+ self.is_a?(EmptyScope)
53
+ end
54
+
55
+ alias_method :tail, :next
56
+
57
+ def match?(manifest_element)
58
+ raise "not implemented"
59
+ end
60
+
61
+ def filter(element, &block)
62
+ return [] if !has_same_type?(element)
63
+ matched_elements = _filter(element)
64
+ if self.last?
65
+ block.call(matched_elements) if block_given?
66
+ matched_elements
67
+ else
68
+ matched_elements.map do |e|
69
+ self.next.filter(e)
70
+ end.compact.flatten
71
+ end
72
+ end
73
+
74
+ def has_same_type?(element)
75
+ raise element.inspect if element.is_a?(Array)
76
+ self.type == element.type
77
+ end
78
+
79
+ def _filter(element)
80
+ raise "not implemented"
81
+ end
82
+
83
+ def to_a
84
+ [self, self.tail.to_a].flatten
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+
91
+ require "mantra/manifest/scope/array_scope"
92
+ require "mantra/manifest/scope/empty_scope"
93
+ # require "mantra/manifest/scope/leaf_scope"
94
+ require "mantra/manifest/scope/hash_scope"
@@ -0,0 +1,47 @@
1
+ require "forwardable"
2
+ require "mantra/manifest/scope"
3
+ require "mantra/manifest/element"
4
+ require "mantra/manifest/ext"
5
+ require "mantra/manifest/root_element"
6
+ require "mantra/manifest/array_element"
7
+ require "mantra/manifest/hash_element"
8
+ require "mantra/manifest/leaf_element"
9
+
10
+ module Mantra
11
+ class Manifest
12
+
13
+ extend Forwardable
14
+ include Mantra::Manifest::Ext
15
+
16
+ class UnknownScopeError < Exception; end
17
+ class FileNotFoundError < Exception; end
18
+
19
+ attr_accessor :root, :file
20
+
21
+ def_delegators :@root, :merge, :to_ruby_object, :traverse,
22
+ :select, :find, :add_node, :fetch, :get
23
+
24
+ def initialize(manifest_object_or_path)
25
+ if manifest_object_or_path.is_a?(String) && File.exist?(manifest_object_or_path)
26
+ manifest_object = YAML.load_file(manifest_object_or_path)
27
+ self.file = manifest_object_or_path
28
+ self.root = Element.create(manifest_object)
29
+ elsif manifest_object_or_path.is_a?(Hash) || manifest_object_or_path.is_a?(Array)
30
+ self.root = Element.create(manifest_object_or_path)
31
+ else
32
+ raise "Don't know how initialize manifest. Expected existing file path or hash, " +
33
+ "got #{manifest_object_or_path.class.inspect}: #{manifest_object_or_path.inspect}"
34
+ end
35
+ end
36
+
37
+ def write(manifest_path)
38
+ File.open(manifest_path, 'w') { |f| f.write(self.to_ruby_object.to_yaml) }
39
+ end
40
+
41
+ def save
42
+ raise FileNotFoundError.new("File is not specified") if self.file.nil?
43
+ self.write(self.file)
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ module Mantra
2
+ class MergeTool
3
+ class Spiff < MergeTool
4
+ type :spiff
5
+
6
+ def templatize(string, scope, start, finish)
7
+ first_part = string[0..start-1]
8
+ value = string[start..finish]
9
+ last_part = string[finish..-1]
10
+ first_part = first_part[3..-1] if first_part.end_with?("(( ")
11
+ last_part = last_part[0..-4] if last_part.end_with?(" ))")
12
+ ["((", "\"#{first_part}\"", scope, "\"#{last_part}\"", "))"].select do |s|
13
+ s != "\"\""
14
+ end.join(" ")
15
+ end
16
+
17
+ def is_template?(string)
18
+ !!string.match(/^\(\(.*\)\)$/)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module Mantra
2
+ class MergeTool
3
+ include Helpers::ObjectWithType
4
+
5
+ def templatize(string, scope, start, finish)
6
+ raise "not implemented"
7
+ end
8
+
9
+ end
10
+ end
11
+
12
+ require "mantra/merge_tool/spiff"
13
+
@@ -0,0 +1,10 @@
1
+ module Mantra
2
+ class Transform
3
+ class Extract < Transform
4
+ type :extract
5
+
6
+
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Mantra
2
+ class Transform
3
+ class ExtractCertificates < Transform
4
+ type :"extract-certificates"
5
+
6
+ description "Extracts certificates from source manifest, " +
7
+ "templatize source manifest and places certificates to target manifest."
8
+ input "source", type: :file,
9
+ validate: :file_exists,
10
+ description: "Source manifest with ceritificates, that will become a template"
11
+ input "target", type: :file,
12
+ description: "Target manifest with extracted ceritificates"
13
+
14
+
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ module Mantra
2
+ class Transform
3
+ class ExtractCertificatesToFiles < Transform
4
+ type :"extract-certificates-to-files"
5
+
6
+ def perform
7
+
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Mantra
2
+ class Transform
3
+ class ExtractSection < Transform
4
+ type :"extract-section"
5
+
6
+
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class AnyInput < Input
5
+ type :any
6
+
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class ArrayInput < Input
5
+ type :array
6
+
7
+ def validate_file_exists(value, name)
8
+ unless File.exist?(value)
9
+ raise ValidationError.new("File does not exist: #{value}")
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class FileInput < Input
5
+ type :file
6
+
7
+ def validate_file_exists(value, name)
8
+ unless File.exist?(value)
9
+ raise ValidationError.new("File does not exist: #{value}")
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class FolderInput < Input
5
+ type :folder
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class HashInput < Input
5
+ type :hash
6
+
7
+ # def validate_file_exists(value, name)
8
+ # unless ::File.exist?(value)
9
+ # raise ValidationError.new("File does not exist: #{value}")
10
+ # end
11
+ # end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ module Mantra
2
+ class Transform
3
+ class Input
4
+ class StringInput < Input
5
+ type :string
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module Mantra
2
+ class Transform
3
+ class Merge < Mantra::Transform
4
+ type :"merge"
5
+ attr_accessor :manifest
6
+
7
+ description "Merge"
8
+
9
+ input "value", description: "value that should be merged in", type: :any
10
+
11
+ input "scope", description: "scope (or path) in stub file that will have current value (for instance meta.networks)",
12
+ type: :string
13
+
14
+ def perform
15
+ @manifest = previous_transform.manifest
16
+ value_manifest_element = Mantra::Manifest::Element.create(self.value).content
17
+
18
+ scope = @manifest.fetch(self.scope)
19
+ scope.each do |s|
20
+ s.merge(value_manifest_element)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ module Mantra
2
+ class Transform
3
+ class Replace < Mantra::Transform
4
+ type :"replace"
5
+ attr_accessor :manifest
6
+
7
+ description "Merge"
8
+
9
+ input "value", description: "value that should be merged in", type: :any
10
+
11
+ input "scope", description: "scope (or path) in stub file that will have current value (for instance meta.networks)",
12
+ type: :string
13
+
14
+ def perform
15
+ @manifest = previous_transform.manifest
16
+ # value_manifest_element = Mantra::Manifest::Element.create(self.value).content
17
+
18
+ scope = @manifest.fetch(self.scope)
19
+ scope.each do |s|
20
+ puts "psssss!!"
21
+ puts s.class
22
+ s.content = self.value
23
+ end
24
+ end
25
+
26
+ def raise_if_no_source_manifest
27
+ if self.source.nil? || !File.exist?(self.source)
28
+ raise Manifest::FileNotFoundError.new("Source manifest does not exist: #{self.source.inspect}")
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,129 @@
1
+ module Mantra
2
+ class Transform
3
+ class TemplatizeIpAddress < Transform
4
+ type :"templatize-ip-address"
5
+ include Mantra::Helpers::RegexpHelper
6
+ include Mantra::Helpers::TemplateHelper
7
+
8
+ description "Templatize ip addresses"
9
+
10
+ input "source", description: "Source manifest with ceritificates, that will become a template",
11
+ type: :file,
12
+ validate: :file_exists
13
+
14
+ input "target", description: "Target manifest with extracted ceritificates",
15
+ type: :file
16
+
17
+ input "quads", description: "quad that is going to be extracted",
18
+ type: :array
19
+
20
+ def perform
21
+ raise_error_if_no_source_manifest
22
+ ensure_yml_file_exist(self.target)
23
+
24
+ source_manifest.traverse do |node|
25
+ template, values = if is_ip_address?(node.content)
26
+ splitter = QuadSplitter.new(node.content, quads)
27
+ ["(( #{splitter.parts(templatize: true).join(" ")} ))", splitter.values]
28
+ elsif is_network_range?(node.content)
29
+ ip_address, network_prefix_size = *node.content.split("/")
30
+ puts "NETWORK RANGE! #{[ip_address, network_prefix_size].inspect}"
31
+ splitter = QuadSplitter.new(ip_address, quads)
32
+ network_range_parts = splitter.parts + ["/#{network_prefix_size}"]
33
+ # puts "PARTS! #{network_range_parts.inspect}"
34
+ resulting_template = "(( #{templatize(network_range_parts).join(" ")} ))"
35
+ [resulting_template, splitter.values]
36
+ elsif is_ip_range?(node.content)
37
+ ip_address1, ip_address2 = *node.content.split("-").map { |ip| ip.strip }
38
+ splitter1 = QuadSplitter.new(ip_address1, quads)
39
+ splitter2 = QuadSplitter.new(ip_address2, quads)
40
+ result = templatize(splitter1.parts + ["-"] + splitter2.parts)
41
+ ["(( #{result.join(" ")} ))", splitter1.values]
42
+ end
43
+ unless template.nil?
44
+ puts template.inspect if node.content == "192.168.3.0/24"
45
+ raise UnknownScopeError.new("Can't templatize non leaf ip address") unless node.leaf?
46
+ node.content = template
47
+ values.each_pair do |scope, value|
48
+ scope_element = Manifest::Element.element_with_selector(scope, value)
49
+ target_manifest.merge(scope_element)
50
+ end
51
+ end
52
+ end
53
+
54
+ source_manifest.save
55
+ target_manifest.save
56
+ end
57
+
58
+ def ip_address_matcher
59
+ "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})"
60
+ end
61
+
62
+ def is_network_range?(value)
63
+ !!value.to_s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/\d{1,2}$/)
64
+ end
65
+
66
+ def is_ip_range?(value)
67
+ !!value.to_s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*-\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
68
+ end
69
+
70
+ def is_ip_address?(value)
71
+ !!value.to_s.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
72
+ end
73
+
74
+ # def templatize(parts)
75
+ # raise "wrong!!!"
76
+ # merged_parts = [parts.shift]
77
+ # parts.each do |p|
78
+ # if !p.is_a?(Scope) && !merged_parts.last.is_a?(Scope)
79
+ # merged_parts.last.concat(p)
80
+ # else
81
+ # merged_parts << p
82
+ # end
83
+ # end
84
+ # merged_parts.select { |p| !p.empty? }.map do |p|
85
+ # p.is_a?(Scope) ? p : "\"#{p}\""
86
+ # end
87
+ # end
88
+
89
+ class QuadSplitter
90
+ include Mantra::Helpers::TemplateHelper
91
+ attr_accessor :values, :parts
92
+ def initialize(ip_address, template_quads)
93
+ @values = {}
94
+ extract_options = template_quads.map do |quad|
95
+ quad["range_object"] = if quad["number"]
96
+ index = quad["number"].to_i - 1
97
+ (index..index)
98
+ elsif quad["range"]
99
+ index1, index2 = *quad["range"].split("-").map { |v| v.strip.to_i - 1 }
100
+ (index1..index2)
101
+ end
102
+ quad
103
+ end
104
+
105
+ template = IpAddressTemplate.new(ip_address)
106
+
107
+ extract_options.each do |option|
108
+ range = option["range_object"]
109
+ value_to_extract = template.quads[range].join(".")
110
+ if option["with_value"].nil? || value_to_extract == option["with_value"]
111
+ template.replace_with_scope(range, option["scope"])
112
+ @values[option["scope"]] = value_to_extract
113
+ end
114
+ end
115
+ @parts = template.parts
116
+ end
117
+
118
+ def parts(options={templatize: false})
119
+ if options[:templatize]
120
+ templatize(@parts)
121
+ else
122
+ @parts
123
+ end
124
+ end
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,63 @@
1
+ module Mantra
2
+ class Transform
3
+ class TemplatizeValue < Transform
4
+ type :"templatize-value"
5
+
6
+ include Mantra::Helpers::RegexpHelper
7
+
8
+ description "Extracts specified value to stub file"
9
+
10
+ input "source", description: "Source manifest with ceritificates, that will become a template",
11
+ type: :file,
12
+ validate: :file_exists
13
+
14
+ input "target", description: "Target manifest with extracted ceritificates",
15
+ type: :file
16
+
17
+ input "value", description: "value you want to templatize (for instance 'domain.com')",
18
+ type: :string
19
+
20
+ input "scope", description: "scope (or path) in stub file that will have current value (for instance meta.networks)",
21
+ type: :string
22
+
23
+ input "in", description: "wildcard matcher (for instance 'https://*.domain.com')",
24
+ type: :string,
25
+ optional: true
26
+
27
+ input "regexp", description: "string with ruby regexp to match values",
28
+ type: :string,
29
+ optional: true
30
+
31
+ def perform
32
+ raise_error_if_no_source_manifest
33
+ ensure_yml_file_exist(self.target)
34
+ # value_matcher = to_regexp(value)
35
+
36
+ raise "scope must not match value wildcard" if scope.match(value)
37
+
38
+ source_manifest.traverse do |node|
39
+ if node.content.to_s.match(value)
40
+ # match = node.content.to_s.match(value_matcher)[0]
41
+ match = node.content.to_s.match(value)[0]
42
+ begin_index = node.content.to_s.index(match)
43
+ end_index = begin_index + match.size
44
+ final_value = merge_tool.templatize(node.content.to_s, scope, begin_index, end_index)
45
+ node.content = final_value
46
+ scope_element = Manifest::Element.element_with_selector(scope, value)
47
+ target_manifest.merge(scope_element)
48
+ end
49
+ end
50
+
51
+ source_manifest.save
52
+ target_manifest.save
53
+ end
54
+
55
+ def raise_if_no_source_manifest
56
+ if self.source.nil? || !File.exist?(self.source)
57
+ raise Manifest::FileNotFoundError.new("Source manifest does not exist: #{self.source.inspect}")
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end