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,60 @@
1
+ module Mantra
2
+ module Helpers
3
+ module ObjectWithType
4
+
5
+ class UnknownType < Exception; end
6
+ class UnspecifiedType < Exception; end
7
+
8
+ def self.included(base)
9
+ base.send :extend, ClassMethods
10
+ base.send :include, InstanceMethods
11
+ end
12
+
13
+ module ClassMethods
14
+ def type(type=nil)
15
+ type.nil? ? @type : (@type = type.to_sym)
16
+ end
17
+
18
+ def alias_type(alias_type=nil)
19
+ alias_type.nil? ? @alias_type : (@alias_type = alias_type.to_sym)
20
+ end
21
+
22
+ def create(options)
23
+ type = options[:type] || options["type"]
24
+ raise UnspecifiedType.new("options hash should contain type") if type.nil?
25
+ subclass = self.find_by_type(type.to_sym)
26
+ if subclass.nil?
27
+ raise UnknownType.new("unknown type #{type}")
28
+ else
29
+ subclass.new(options)
30
+ end
31
+ end
32
+
33
+ def find_by_type(type)
34
+ self.subclasses.find { |s| s.type == type || s.alias_type == type }
35
+ end
36
+
37
+ def inherited(subclass)
38
+ subclasses << subclass
39
+ super
40
+ end
41
+
42
+ def subclasses
43
+ @subclasses ||= []
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ def type
49
+ self.class.type
50
+ end
51
+
52
+ attr_accessor :options
53
+
54
+ def initialize(options={})
55
+ @options = options
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,12 @@
1
+ module Mantra
2
+ module Helpers
3
+ module RegexpHelper
4
+
5
+ def to_regexp(string)
6
+ escaped_string = Regexp.escape(string)
7
+ Regexp.new("^#{escaped_string.gsub("\\*", ".+")}$")
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,70 @@
1
+ class IpAddressTemplate
2
+ class Scope < String
3
+ attr_accessor :scope, :value
4
+ def initialize(options)
5
+ @value = options[:value]
6
+ @scope = options[:scope]
7
+ super(options[:scope])
8
+ end
9
+ def is_scope?
10
+ true
11
+ end
12
+ end
13
+
14
+ class Value < String
15
+ def is_scope?
16
+ false
17
+ end
18
+ end
19
+
20
+ attr_accessor :quads
21
+ def initialize(ip_address)
22
+ @quads = ip_address.split(".").map { |q| Value.new(q) }
23
+ end
24
+
25
+ def replace_with_scope(range, scope)
26
+ range.to_a.each do |i|
27
+ @quads[i] = Scope.new(scope: scope, value: @quads[i])
28
+ end
29
+ end
30
+
31
+ alias_method :replace_quad_range_with_scope, :replace_with_scope
32
+
33
+ def parts
34
+ result = [@quads.first]
35
+ @quads[1..-1].each do |q|
36
+ if q.is_scope? && result.last.is_scope? && q.scope == result.last.scope
37
+ result.last.value = [result.last.value, q.value].join(".")
38
+ else
39
+ result << Value.new(".") << q
40
+ end
41
+ end
42
+ result
43
+ end
44
+ end
45
+
46
+ module Mantra
47
+ module Helpers
48
+ module TemplateHelper
49
+
50
+ def is_scope?(v)
51
+ v.respond_to?(:is_scope?) && v.is_scope?
52
+ end
53
+
54
+ def templatize(parts)
55
+ merged_parts = [parts.shift]
56
+ parts.each do |p|
57
+ if !is_scope?(p) && !is_scope?(merged_parts.last)
58
+ merged_parts.last.concat(p)
59
+ else
60
+ merged_parts << p
61
+ end
62
+ end
63
+ merged_parts.select { |p| !p.empty? }.map do |p|
64
+ is_scope?(p) ? p : "\"#{p}\""
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+ module Mantra
2
+ class Manifest
3
+ class ArrayElement < Element
4
+
5
+ type :array
6
+
7
+ def content=(content)
8
+ @content = content.map do |item|
9
+ Element.create(item, self)
10
+ end
11
+ end
12
+
13
+ def merge(element, options={})
14
+ raise merge_conflict_error(element) unless self.can_merge?(element)
15
+ elements_to_add = element.content.dup
16
+ merge_by_name(elements_to_add, options)
17
+ merge_by_value(elements_to_add, options)
18
+ self.content.concat(elements_to_add)
19
+ self
20
+ end
21
+
22
+ def find_children_by_scope(scope)
23
+ return [] unless scope.array?
24
+ self.content.map do |element|
25
+ if scope.match?(element)
26
+ if scope.has_next?
27
+ element.find_children_by_scope(scope.next)
28
+ else
29
+ element
30
+ end
31
+ end
32
+ end.flatten.compact
33
+ end
34
+
35
+ def each(path, &block)
36
+ elements = select(path)
37
+ elements.each(&block)
38
+ end
39
+
40
+ def selector_for(element)
41
+ self.content.index(element).to_s
42
+ end
43
+
44
+ def merge_by_value(elements, options={})
45
+ self.content.each do |self_element|
46
+ element_with_the_same_value = elements.find do |element_to_add|
47
+ self_element.content == element_to_add.content
48
+ end
49
+ unless element_with_the_same_value.nil?
50
+ self_element.merge(element_with_the_same_value, options)
51
+ elements.delete(element_with_the_same_value)
52
+ end
53
+ end
54
+ end
55
+
56
+ def children
57
+ self.content
58
+ end
59
+
60
+ def merge_by_name(elements, options={})
61
+ self.content.each do |self_element|
62
+ element_with_the_same_name = elements.find do |element_to_add|
63
+ self_element.has_equal_name?(element_to_add)
64
+ end
65
+ unless element_with_the_same_name.nil?
66
+ self_element.merge(element_with_the_same_name, options)
67
+ elements.delete(element_with_the_same_name)
68
+ end
69
+ end
70
+ end
71
+
72
+ def to_ruby_object
73
+ self.content.map { |element| element.to_ruby_object }
74
+ end
75
+
76
+ def traverse(&block)
77
+ self.content.each do |value|
78
+ value.traverse(&block)
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,175 @@
1
+ module Mantra
2
+ class Manifest
3
+ class Element
4
+
5
+ class MergeConflictError < Exception; end
6
+ class UnknownScopeError < Exception; end
7
+
8
+ include Helpers::ObjectWithType
9
+ include Helpers::RegexpHelper
10
+
11
+ attr_accessor :content, :parent
12
+
13
+ def self.create(content, parent = nil)
14
+ return RootElement.new(content, parent) if parent.nil?
15
+ case content
16
+ when Element then Element.create(content.content, parent)
17
+ when Array then ArrayElement.new(content, parent)
18
+ when Hash then HashElement.new(content, parent)
19
+ else LeafElement.new(content, parent)
20
+ end
21
+ end
22
+
23
+ def initialize(content, parent)
24
+ self.content = content
25
+ self.parent = parent
26
+ end
27
+
28
+ # this method mimics ruby Hash#fetch method
29
+ def fetch(scope, &block)
30
+ current_scope = Scope.parse(scope)
31
+ element = self.root? ? self.content : self
32
+ current_scope.filter(element, &block)
33
+ end
34
+
35
+ alias_method :select, :fetch
36
+
37
+ def children
38
+ raise "not implemented"
39
+ end
40
+
41
+ def content=(content)
42
+ raise "#content=() method is not implemented"
43
+ end
44
+
45
+ def name
46
+ raise "unknown name"
47
+ end
48
+
49
+ def find(string_scope)
50
+ self.fetch(string_scope).first
51
+ end
52
+
53
+ def get(string_scope)
54
+ result = self.find(string_scope)
55
+ result.nil? ? nil : result.to_ruby_object
56
+ end
57
+
58
+ def has_name?
59
+ false
60
+ end
61
+
62
+ def has_equal_name?(element)
63
+ false
64
+ end
65
+
66
+ %i(leaf hash array root).each do |type|
67
+ define_method "#{type}?" do
68
+ self.type == type
69
+ end
70
+ end
71
+
72
+ def path
73
+ case self.parent.type
74
+ when :hash then "#{self.parent.path}#{self.parent.parent.root? ? "" : "."}#{self.selector}"
75
+ when :array then "#{self.parent.path}[#{self.selector}]"
76
+ when :leaf then raise "leaf can't be parent"
77
+ end
78
+ end
79
+
80
+ def selector
81
+ case self.parent.type
82
+ when :array
83
+ self.has_name? ? "name=#{self.name}" : ""
84
+ when :hash
85
+ self.parent.selector_for(self)
86
+ when :root
87
+ ""
88
+ else
89
+ raise "don't know how to build selector"
90
+ end
91
+ end
92
+
93
+ def merge(element, options={})
94
+ raise "not implemented"
95
+ end
96
+
97
+ def to_ruby_object
98
+ raise "not implemented"
99
+ end
100
+
101
+ def merge_conflict_error(element)
102
+ object = element.respond_to?(:to_ruby_object) ? element.to_ruby_object : element.inspect
103
+ MergeConflictError.new("merge conflicts: types: #{self.class} against #{element.class}, values: #{self.to_ruby_object} with #{object}")
104
+ end
105
+
106
+ def can_merge?(element)
107
+ if element.respond_to?(:type)
108
+ self.type == element.type
109
+ else
110
+ self.class == element.class
111
+ end
112
+ end
113
+
114
+ def delete(selector)
115
+ raise "not implemented"
116
+ end
117
+
118
+ def method_missing(method_name, *arguments, &block)
119
+ if self.content.respond_to?(method_name)
120
+ self.content.send(method_name, *arguments, &block)
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ def respond_to_missing?(method_name, include_private = false)
127
+ self.content.respond_to?(method_name) || super
128
+ end
129
+
130
+ def traverse(&block)
131
+ raise "not implemented"
132
+ end
133
+
134
+ def match_selector?(selector)
135
+ selector.empty?
136
+ end
137
+
138
+ def path_exist?(path)
139
+ !self.select(path).empty?
140
+ end
141
+
142
+ def add_node(selector, value)
143
+ raise "not implemented"
144
+ end
145
+
146
+ def self.element_with_selector(selector, value)
147
+ object_to_add = {}
148
+ parts = selector.split(".")
149
+ last_key = parts.pop
150
+ last_node = parts.inject(object_to_add) do |h, part|
151
+ h[part] = {}; h[part]
152
+ end
153
+ last_node[last_key] = value
154
+ Element.create(object_to_add)
155
+ end
156
+
157
+ def split_selector(selector, matcher_regex)
158
+ matcher = selector.match(matcher_regex)
159
+ raise UnknownScopeError.new("Unknown selector: #{selector}") if matcher.nil?
160
+ head_selector = matcher[1]
161
+ tail_selector = matcher[2]
162
+ return head_selector, tail_selector
163
+ end
164
+
165
+ def array_selector?(selector)
166
+ !!selector.match(/\[(.+\=.+|\d+)?\].*/)
167
+ end
168
+
169
+ def hash_selector?(selector)
170
+ !!selector.match(/[a-z0-9]+/)
171
+ end
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,33 @@
1
+ module Mantra
2
+ class Manifest
3
+ module Ext
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ base.send :include, InstanceMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ def properties
15
+ job_properties = Element.create({}).content
16
+ job_properties_array = self.jobs.map do |job|
17
+ job.hash? ? job.content["properties"] : nil
18
+ end.compact
19
+ job_properties_array.each do |jp|
20
+ job_properties.merge(jp)
21
+ end
22
+ manifest_properties = self.root.child.hash? ? self.root.child.content["properties"] : Element.create({}).content
23
+ manifest_properties.merge(job_properties)
24
+ Element.create(manifest_properties)
25
+ end
26
+
27
+ def jobs
28
+ self.root.content.hash? ? self.root.content.content["jobs"] : nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ module Mantra
2
+ class Manifest
3
+ class HashElement < Element
4
+ type :hash
5
+
6
+ def content=(content) # content is hash
7
+ @content = content.to_a.inject({}) do |result, pair|
8
+ key, value = *pair
9
+ result[key] = Element.create(value, self)
10
+ result
11
+ end
12
+ end
13
+
14
+ def merge(element, options={})
15
+ raise merge_conflict_error(element) unless self.can_merge?(element)
16
+ self.content.merge!(element.content) do |_, self_element, element_to_merge|
17
+ self_element.merge(element_to_merge, options)
18
+ end
19
+ self
20
+ end
21
+
22
+ def to_ruby_object
23
+ self.content.to_a.inject({}) do |result, pair|
24
+ key, element = *pair
25
+ result[key] = element.to_ruby_object
26
+ result
27
+ end
28
+ end
29
+
30
+ def has_equal_name?(element)
31
+ self.has_name? && element.has_name? && self.name == element.name
32
+ end
33
+
34
+ def has_name?
35
+ self.content.has_key?("name")
36
+ end
37
+
38
+ def children
39
+ self.content.values
40
+ end
41
+
42
+ def name
43
+ raise "name should be a value, not node" if !self.content["name"].leaf?
44
+ self.content["name"].content || raise("no name for #{self.inspect}")
45
+ end
46
+
47
+ def selector_for(element)
48
+ self.content.each_pair do |key, value|
49
+ return key if value == element
50
+ end
51
+ raise "can't find key for object: #{element.to_ruby_object.inspect}"
52
+ end
53
+
54
+ def method_missing(method_name, *arguments, &block)
55
+ if self.content.has_key?(method_name.to_s) && arguments.empty?
56
+ self.content[method_name.to_s]
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def respond_to_missing?(method_name, include_private = false)
63
+ self.content.has_key?(method_name.to_s) || super
64
+ end
65
+
66
+ def traverse(&block)
67
+ self.content.each_value do |value|
68
+ value.traverse(&block)
69
+ end
70
+ end
71
+
72
+ def find_children_by_scope(scope)
73
+ return [] unless scope.hash?
74
+ self.content.each_pair.map do |pair|
75
+ key, value = *pair
76
+ if scope.match?(key, value)
77
+ if scope.has_next?
78
+ element.find_children_by_scope(scope.next)
79
+ else
80
+ element
81
+ end
82
+ end
83
+ end.flatten.compact
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,38 @@
1
+ module Mantra
2
+ class Manifest
3
+ class LeafElement < Element
4
+
5
+ type :leaf
6
+
7
+ def content=(content)
8
+ @content = content
9
+ end
10
+
11
+ def merge(element, options={})
12
+ raise merge_conflict_error(element) unless self.can_merge?(element)
13
+ if !options[:force]
14
+ raise MergeConflictError.new("value conflict detected: #{self.content} != #{element.content}") if self.content != element.content
15
+ end
16
+ self
17
+ end
18
+
19
+ def to_ruby_object
20
+ self.content
21
+ end
22
+
23
+ def traverse(&block)
24
+ block.call(self)
25
+ end
26
+
27
+ def add_node(selector, value)
28
+ raise UnknownScopeError.new("Can't add nodes to leaf") unless selector.empty?
29
+ raise MergeConflictError.new("Leaf already exists with another value: #{self.content} != #{value}") unless self.content != value
30
+ end
31
+
32
+ def children
33
+ []
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Mantra
2
+ class Manifest
3
+ class RootElement < Element
4
+
5
+ type :root
6
+
7
+ def content=(content)
8
+ @content = Element.create(content, self)
9
+ end
10
+
11
+ def path
12
+ ""
13
+ end
14
+
15
+ def merge(element, options={})
16
+ raise merge_conflict_error(element) unless self.can_merge?(element)
17
+ self.content.merge(element.content, options)
18
+ end
19
+
20
+ def to_ruby_object
21
+ self.content.to_ruby_object
22
+ end
23
+
24
+ def traverse(&block)
25
+ self.content.traverse(&block)
26
+ end
27
+
28
+ def child
29
+ self.content
30
+ end
31
+
32
+ def children
33
+ self.content
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ module Mantra
2
+ class Manifest
3
+ class Scope
4
+ class ArrayScope < Scope
5
+ class UnknownFunction < Exception; end
6
+
7
+ type :array
8
+
9
+ # returns array of children that are
10
+ def _filter(element)
11
+ case scope
12
+ when /^\d+$/
13
+ index = scope.to_i
14
+ [element.content[index]]
15
+ when /^.+\=.+$/
16
+ key_wildcard, value_wildcard = scope.split("=")
17
+ key_matcher, value_matcher = to_regexp(key_wildcard), to_regexp(value_wildcard)
18
+ element.content.select { |e| e.hash? }.select do |e|
19
+ e.each_pair.any? do |pair|
20
+ k, v = *pair
21
+ k.match(key_matcher) && v.match(value_matcher)
22
+ end
23
+ end
24
+ when /^\:\:.+$/
25
+ function_name = self.scope[2..-1]
26
+ if !allowed_function_names.include?(function_name)
27
+ raise ScopeParseError.new("Unknown function: #{function_name}")
28
+ end
29
+ self.send(function_name, element)
30
+ when ""
31
+ element.content
32
+ else
33
+ raise ScopeParseError.new("Don't know how to apply scope to array " +
34
+ "(scope: #{self.scope.inspect}, array: #{element.to_ruby_object.inspect})")
35
+ end
36
+ end
37
+
38
+ def allowed_function_names
39
+ %w(last first)
40
+ end
41
+
42
+ def last(element)
43
+ [element.content.last]
44
+ end
45
+
46
+ def first(element)
47
+ [element.content.first]
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ module Mantra
2
+ class Manifest
3
+ class Scope
4
+ class EmptyScope < Scope
5
+
6
+ type :empty
7
+
8
+ # returns array of children that matches this scope
9
+ def _filter(element)
10
+ [element]
11
+ end
12
+
13
+ def last?
14
+ true
15
+ end
16
+
17
+ def has_same_type?(element)
18
+ true
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Mantra
2
+ class Manifest
3
+ class Scope
4
+ class HashScope < Scope
5
+
6
+ type :hash
7
+
8
+ # returns array of children that matches this scope
9
+ def _filter(element)
10
+ scope_regexp = to_regexp(scope)
11
+ element.content.each_pair.map do |pair|
12
+ key, value = *pair
13
+ key.match(scope_regexp) ? value : nil
14
+ end.compact
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
File without changes