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