mantra 0.1.0 → 0.2.0

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.
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