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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -2
- data/README.md +126 -16
- data/bin/mantra +17 -0
- data/ci/README.md +6 -0
- data/ci/docker/Dockerfile +20 -0
- data/ci/docker/build-docker-image +10 -0
- data/ci/pipeline.yml +42 -0
- data/ci/scripts/run-tests +3 -0
- data/ci/stub.example.yml +6 -0
- data/ci/stub.yml +33 -0
- data/ci/tasks/run-tests-dev.yml +10 -0
- data/ci/tasks/run-tests-stable.yml +13 -0
- data/lib/mantra/certificate.rb +35 -0
- data/lib/mantra/certificates/cortege.rb +64 -0
- data/lib/mantra/certificates/group.rb +26 -0
- data/lib/mantra/command.rb +51 -0
- data/lib/mantra/commands/find.rb +38 -0
- data/lib/mantra/commands/transform.rb +38 -0
- data/lib/mantra/helpers/object_with_type.rb +60 -0
- data/lib/mantra/helpers/regexp_helper.rb +12 -0
- data/lib/mantra/helpers/template_helper.rb +70 -0
- data/lib/mantra/manifest/array_element.rb +84 -0
- data/lib/mantra/manifest/element.rb +175 -0
- data/lib/mantra/manifest/ext.rb +33 -0
- data/lib/mantra/manifest/hash_element.rb +88 -0
- data/lib/mantra/manifest/leaf_element.rb +38 -0
- data/lib/mantra/manifest/root_element.rb +38 -0
- data/lib/mantra/manifest/scope/array_scope.rb +53 -0
- data/lib/mantra/manifest/scope/empty_scope.rb +24 -0
- data/lib/mantra/manifest/scope/hash_scope.rb +20 -0
- data/lib/mantra/manifest/scope/leaf_scope.rb +0 -0
- data/lib/mantra/manifest/scope.rb +94 -0
- data/lib/mantra/manifest.rb +47 -0
- data/lib/mantra/merge_tool/spiff.rb +23 -0
- data/lib/mantra/merge_tool.rb +13 -0
- data/lib/mantra/transform/extract.rb +10 -0
- data/lib/mantra/transform/extract_certificates.rb +18 -0
- data/lib/mantra/transform/extract_certificates_to_files.rb +12 -0
- data/lib/mantra/transform/extract_section.rb +10 -0
- data/lib/mantra/transform/inputs/any.rb +10 -0
- data/lib/mantra/transform/inputs/array.rb +16 -0
- data/lib/mantra/transform/inputs/file.rb +16 -0
- data/lib/mantra/transform/inputs/folder.rb +9 -0
- data/lib/mantra/transform/inputs/hash.rb +16 -0
- data/lib/mantra/transform/inputs/string.rb +9 -0
- data/lib/mantra/transform/merge.rb +26 -0
- data/lib/mantra/transform/replace.rb +34 -0
- data/lib/mantra/transform/templatize_ip_address.rb +129 -0
- data/lib/mantra/transform/templatize_value.rb +63 -0
- data/lib/mantra/transform.rb +118 -0
- data/lib/mantra/version.rb +1 -1
- data/lib/mantra.rb +9 -1
- data/mantra.gemspec +6 -5
- metadata +69 -8
- data/bin/console +0 -14
- 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,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,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,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,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
|