leap_cli 1.2.5
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.
- data/bin/leap +81 -0
- data/lib/core_ext/boolean.rb +14 -0
- data/lib/core_ext/hash.rb +35 -0
- data/lib/core_ext/json.rb +42 -0
- data/lib/core_ext/nil.rb +5 -0
- data/lib/core_ext/string.rb +14 -0
- data/lib/leap/platform.rb +52 -0
- data/lib/leap_cli/commands/ca.rb +430 -0
- data/lib/leap_cli/commands/clean.rb +16 -0
- data/lib/leap_cli/commands/compile.rb +134 -0
- data/lib/leap_cli/commands/deploy.rb +172 -0
- data/lib/leap_cli/commands/facts.rb +93 -0
- data/lib/leap_cli/commands/inspect.rb +140 -0
- data/lib/leap_cli/commands/list.rb +122 -0
- data/lib/leap_cli/commands/new.rb +126 -0
- data/lib/leap_cli/commands/node.rb +272 -0
- data/lib/leap_cli/commands/pre.rb +99 -0
- data/lib/leap_cli/commands/shell.rb +67 -0
- data/lib/leap_cli/commands/test.rb +55 -0
- data/lib/leap_cli/commands/user.rb +140 -0
- data/lib/leap_cli/commands/util.rb +50 -0
- data/lib/leap_cli/commands/vagrant.rb +201 -0
- data/lib/leap_cli/config/macros.rb +369 -0
- data/lib/leap_cli/config/manager.rb +369 -0
- data/lib/leap_cli/config/node.rb +37 -0
- data/lib/leap_cli/config/object.rb +336 -0
- data/lib/leap_cli/config/object_list.rb +174 -0
- data/lib/leap_cli/config/secrets.rb +43 -0
- data/lib/leap_cli/config/tag.rb +18 -0
- data/lib/leap_cli/constants.rb +7 -0
- data/lib/leap_cli/leapfile.rb +97 -0
- data/lib/leap_cli/load_paths.rb +15 -0
- data/lib/leap_cli/log.rb +166 -0
- data/lib/leap_cli/logger.rb +216 -0
- data/lib/leap_cli/markdown_document_listener.rb +134 -0
- data/lib/leap_cli/path.rb +84 -0
- data/lib/leap_cli/remote/leap_plugin.rb +204 -0
- data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
- data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
- data/lib/leap_cli/remote/tasks.rb +36 -0
- data/lib/leap_cli/requirements.rb +19 -0
- data/lib/leap_cli/ssh_key.rb +130 -0
- data/lib/leap_cli/util/remote_command.rb +110 -0
- data/lib/leap_cli/util/secret.rb +54 -0
- data/lib/leap_cli/util/x509.rb +32 -0
- data/lib/leap_cli/util.rb +431 -0
- data/lib/leap_cli/version.rb +9 -0
- data/lib/leap_cli.rb +46 -0
- data/lib/lib_ext/capistrano_connections.rb +16 -0
- data/lib/lib_ext/gli.rb +52 -0
- data/lib/lib_ext/markdown_document_listener.rb +122 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
- data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
- data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
- data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
- data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
- data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
- data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
- data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
- data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
- data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
- data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
- data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
- data/vendor/rsync_command/lib/rsync_command.rb +96 -0
- data/vendor/rsync_command/test/rsync_test.rb +74 -0
- data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
- data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
- data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
- metadata +345 -0
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'json/pure' # pure ruby implementation is required for our sorted trick to work.
|
3
|
+
|
4
|
+
if $ruby_version < [1,9]
|
5
|
+
$KCODE = 'UTF8'
|
6
|
+
end
|
7
|
+
require 'ya2yaml' # pure ruby yaml
|
8
|
+
|
9
|
+
require 'leap_cli/config/macros'
|
10
|
+
|
11
|
+
module LeapCli
|
12
|
+
module Config
|
13
|
+
#
|
14
|
+
# This class represents the configuration for a single node, service, or tag.
|
15
|
+
# Also, all the nested hashes are also of this type.
|
16
|
+
#
|
17
|
+
# It is called 'object' because it corresponds to an Object in JSON.
|
18
|
+
#
|
19
|
+
class Object < Hash
|
20
|
+
|
21
|
+
include Config::Macros
|
22
|
+
|
23
|
+
attr_reader :node
|
24
|
+
attr_reader :manager
|
25
|
+
alias :global :manager
|
26
|
+
|
27
|
+
def initialize(manager=nil, node=nil)
|
28
|
+
# keep a global pointer around to the config manager. used a lot in the eval strings and templates
|
29
|
+
# (which are evaluated in the context of Config::Object)
|
30
|
+
@manager = manager
|
31
|
+
|
32
|
+
# an object that is a node as @node equal to self, otherwise all the child objects point back to the top level node.
|
33
|
+
@node = node || self
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# We use pure ruby yaml exporter ya2yaml instead of SYCK or PSYCH because it
|
38
|
+
# allows us greater compatibility regardless of installed ruby version and
|
39
|
+
# greater control over how the yaml is exported (sorted keys, in particular).
|
40
|
+
#
|
41
|
+
def dump
|
42
|
+
evaluate
|
43
|
+
ya2yaml(:syck_compatible => true)
|
44
|
+
end
|
45
|
+
|
46
|
+
def dump_json
|
47
|
+
evaluate
|
48
|
+
JSON.sorted_generate(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def evaluate
|
52
|
+
evaluate_everything
|
53
|
+
late_evaluate_everything
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
## FETCHING VALUES
|
58
|
+
##
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
get(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# make hash addressable like an object (e.g. obj['name'] available as obj.name)
|
66
|
+
#
|
67
|
+
def method_missing(method, *args, &block)
|
68
|
+
get!(method)
|
69
|
+
end
|
70
|
+
|
71
|
+
def get(key)
|
72
|
+
begin
|
73
|
+
get!(key)
|
74
|
+
rescue NoMethodError
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# override behavior of #default() from Hash
|
80
|
+
def default
|
81
|
+
get!('default')
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Like a normal Hash#[], except:
|
86
|
+
#
|
87
|
+
# (1) lazily eval dynamic values when we encounter them. (i.e. strings that start with "= ")
|
88
|
+
#
|
89
|
+
# (2) support for nested references in a single string (e.g. ['a.b'] is the same as ['a']['b'])
|
90
|
+
# the dot path is always absolute, starting at the top-most object.
|
91
|
+
#
|
92
|
+
def get!(key)
|
93
|
+
key = key.to_s
|
94
|
+
if key =~ /\./
|
95
|
+
# for keys with with '.' in them, we start from the root object (@node).
|
96
|
+
keys = key.split('.')
|
97
|
+
value = @node.get!(keys.first)
|
98
|
+
if value.is_a? Config::Object
|
99
|
+
value.get!(keys[1..-1].join('.'))
|
100
|
+
else
|
101
|
+
value
|
102
|
+
end
|
103
|
+
elsif self.has_key?(key)
|
104
|
+
fetch_value(key)
|
105
|
+
else
|
106
|
+
raise NoMethodError.new(key, "No method '#{key}' for #{self.class}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
## COPYING
|
112
|
+
##
|
113
|
+
|
114
|
+
#
|
115
|
+
# A deep (recursive) merge with another Config::Object.
|
116
|
+
#
|
117
|
+
# If prefer_self is set to true, the value from self will be picked when there is a conflict
|
118
|
+
# that cannot be merged.
|
119
|
+
#
|
120
|
+
# Merging rules:
|
121
|
+
#
|
122
|
+
# - If a value is a hash, we recursively merge it.
|
123
|
+
# - If the value is simple, like a string, the new one overwrites the value.
|
124
|
+
# - If the value is an array:
|
125
|
+
# - If both old and new values are arrays, the new one replaces the old.
|
126
|
+
# - If one of the values is simple but the other is an array, the simple is added to the array.
|
127
|
+
#
|
128
|
+
def deep_merge!(object, prefer_self=false)
|
129
|
+
object.each do |key,new_value|
|
130
|
+
old_value = self.fetch key, nil
|
131
|
+
|
132
|
+
# clean up boolean
|
133
|
+
new_value = true if new_value == "true"
|
134
|
+
new_value = false if new_value == "false"
|
135
|
+
old_value = true if old_value == "true"
|
136
|
+
old_value = false if old_value == "false"
|
137
|
+
|
138
|
+
# merge hashes
|
139
|
+
if old_value.is_a?(Hash) || new_value.is_a?(Hash)
|
140
|
+
value = Config::Object.new(@manager, @node)
|
141
|
+
old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?)
|
142
|
+
new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?)
|
143
|
+
|
144
|
+
# merge nil
|
145
|
+
elsif new_value.nil?
|
146
|
+
value = old_value
|
147
|
+
elsif old_value.nil?
|
148
|
+
value = new_value
|
149
|
+
|
150
|
+
# merge arrays when one value is not an array
|
151
|
+
elsif old_value.is_a?(Array) && !new_value.is_a?(Array)
|
152
|
+
(value = (old_value.dup << new_value).compact.uniq).delete('REQUIRED')
|
153
|
+
elsif new_value.is_a?(Array) && !old_value.is_a?(Array)
|
154
|
+
(value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED')
|
155
|
+
|
156
|
+
# catch errors
|
157
|
+
elsif type_mismatch?(old_value, new_value)
|
158
|
+
raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [
|
159
|
+
old_value.inspect, old_value.class,
|
160
|
+
new_value.inspect, new_value.class,
|
161
|
+
key, self.class
|
162
|
+
]
|
163
|
+
|
164
|
+
# merge strings, numbers, and sometimes arrays
|
165
|
+
else
|
166
|
+
if prefer_self
|
167
|
+
value = old_value
|
168
|
+
else
|
169
|
+
value = new_value
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# save value
|
174
|
+
self[key] = value
|
175
|
+
end
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# like a reverse deep merge
|
181
|
+
# (self takes precedence)
|
182
|
+
#
|
183
|
+
def inherit_from!(object)
|
184
|
+
self.deep_merge!(object, true)
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Make a copy of ourselves, except only including the specified keys.
|
189
|
+
#
|
190
|
+
# Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
|
191
|
+
#
|
192
|
+
def pick(*keys)
|
193
|
+
keys.map(&:to_s).inject(self.class.new(@manager)) do |hsh, key|
|
194
|
+
value = self.get(key)
|
195
|
+
if !value.nil?
|
196
|
+
hsh[key.gsub('.','_')] = value
|
197
|
+
end
|
198
|
+
hsh
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
#
|
205
|
+
# walks the object tree, eval'ing all the attributes that are dynamic ruby (e.g. value starts with '= ')
|
206
|
+
#
|
207
|
+
def evaluate_everything
|
208
|
+
keys.each do |key|
|
209
|
+
obj = fetch_value(key)
|
210
|
+
if is_required_value_not_set?(obj)
|
211
|
+
Util::log 0, :warning, "required key \"#{key}\" is not set in node \"#{node.name}\"."
|
212
|
+
elsif obj.is_a? Config::Object
|
213
|
+
obj.evaluate_everything
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# some keys need to be evaluated 'late', after all the other keys have been evaluated.
|
220
|
+
#
|
221
|
+
def late_evaluate_everything
|
222
|
+
if @late_eval_list
|
223
|
+
@late_eval_list.each do |key, value|
|
224
|
+
self[key] = evaluate_now(key, value)
|
225
|
+
if is_required_value_not_set?(self[key])
|
226
|
+
Util::log 0, :warning, "required key \"#{key}\" is not set in node \"#{node.name}\"."
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
values.each do |obj|
|
231
|
+
if obj.is_a? Config::Object
|
232
|
+
obj.late_evaluate_everything
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
#
|
240
|
+
# fetches the value for the key, evaluating the value as ruby if it begins with '='
|
241
|
+
#
|
242
|
+
def fetch_value(key)
|
243
|
+
value = fetch(key, nil)
|
244
|
+
if value.is_a?(String) && value =~ /^=/
|
245
|
+
if value =~ /^=> (.*)$/
|
246
|
+
value = evaluate_later(key, $1)
|
247
|
+
elsif value =~ /^= (.*)$/
|
248
|
+
value = evaluate_now(key, $1)
|
249
|
+
end
|
250
|
+
self[key] = value
|
251
|
+
end
|
252
|
+
return value
|
253
|
+
end
|
254
|
+
|
255
|
+
def evaluate_later(key, value)
|
256
|
+
@late_eval_list ||= []
|
257
|
+
@late_eval_list << [key, value]
|
258
|
+
'<evaluate later>'
|
259
|
+
end
|
260
|
+
|
261
|
+
def evaluate_now(key, value)
|
262
|
+
result = nil
|
263
|
+
if LeapCli.log_level >= 2
|
264
|
+
result = @node.instance_eval(value)
|
265
|
+
else
|
266
|
+
begin
|
267
|
+
result = @node.instance_eval(value)
|
268
|
+
rescue SystemStackError => exc
|
269
|
+
Util::log 0, :error, "while evaluating node '#{@node.name}'"
|
270
|
+
Util::log 0, "offending key: #{key}", :indent => 1
|
271
|
+
Util::log 0, "offending string: #{value}", :indent => 1
|
272
|
+
Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1
|
273
|
+
raise SystemExit.new(1)
|
274
|
+
rescue FileMissing => exc
|
275
|
+
Util::bail! do
|
276
|
+
if exc.options[:missing]
|
277
|
+
Util::log :missing, exc.options[:missing].gsub('$node', @node.name)
|
278
|
+
else
|
279
|
+
Util::log :error, "while evaluating node '#{@node.name}'"
|
280
|
+
Util::log "offending key: #{key}", :indent => 1
|
281
|
+
Util::log "offending string: #{value}", :indent => 1
|
282
|
+
Util::log "error message: no file '#{exc}'", :indent => 1
|
283
|
+
end
|
284
|
+
end
|
285
|
+
rescue AssertionFailed => exc
|
286
|
+
Util.bail! do
|
287
|
+
Util::log :failed, "assertion while evaluating node '#{@node.name}'"
|
288
|
+
Util::log 'assertion: %s' % exc.assertion, :indent => 1
|
289
|
+
Util::log "offending key: #{key}", :indent => 1
|
290
|
+
end
|
291
|
+
rescue SyntaxError, StandardError => exc
|
292
|
+
Util::bail! do
|
293
|
+
Util::log :error, "while evaluating node '#{@node.name}'"
|
294
|
+
Util::log "offending key: #{key}", :indent => 1
|
295
|
+
Util::log "offending string: #{value}", :indent => 1
|
296
|
+
Util::log "error message: #{exc.inspect}", :indent => 1
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
return result
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# when merging, we raise an error if this method returns true for the two values.
|
305
|
+
#
|
306
|
+
def type_mismatch?(old_value, new_value)
|
307
|
+
if old_value.is_a?(Boolean) && new_value.is_a?(Boolean)
|
308
|
+
# note: FalseClass and TrueClass are different classes
|
309
|
+
# so we can't do old_value.class == new_value.class
|
310
|
+
return false
|
311
|
+
elsif old_value.is_a?(String) && old_value =~ /^=/
|
312
|
+
# pass through macros, since we don't know what the type will eventually be.
|
313
|
+
return false
|
314
|
+
elsif new_value.is_a?(String) && new_value =~ /^=/
|
315
|
+
return false
|
316
|
+
elsif old_value.class == new_value.class
|
317
|
+
return false
|
318
|
+
else
|
319
|
+
return true
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
#
|
324
|
+
# returns true if the value has not been changed and the default is "REQUIRED"
|
325
|
+
#
|
326
|
+
def is_required_value_not_set?(value)
|
327
|
+
if value.is_a? Array
|
328
|
+
value == ["REQUIRED"]
|
329
|
+
else
|
330
|
+
value == "REQUIRED"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
end # class
|
335
|
+
end # module
|
336
|
+
end # module
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module LeapCli
|
2
|
+
module Config
|
3
|
+
#
|
4
|
+
# A list of Config::Object instances (internally stored as a hash)
|
5
|
+
#
|
6
|
+
class ObjectList < Hash
|
7
|
+
|
8
|
+
def initialize(config=nil)
|
9
|
+
if config
|
10
|
+
self.add(config['name'], config)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# If the key is a string, the Config::Object it references is returned.
|
16
|
+
#
|
17
|
+
# If the key is a hash, we treat it as a condition and filter all the Config::Objects using the condition.
|
18
|
+
# A new ObjectList is returned.
|
19
|
+
#
|
20
|
+
# Examples:
|
21
|
+
#
|
22
|
+
# nodes['vpn1']
|
23
|
+
# node named 'vpn1'
|
24
|
+
#
|
25
|
+
# nodes[:public_dns => true]
|
26
|
+
# all nodes with public dns
|
27
|
+
#
|
28
|
+
# nodes[:services => 'openvpn', :services => 'tor']
|
29
|
+
# nodes with openvpn OR tor service
|
30
|
+
#
|
31
|
+
# nodes[:services => 'openvpn'][:tags => 'production']
|
32
|
+
# nodes with openvpn AND are production
|
33
|
+
#
|
34
|
+
def [](key)
|
35
|
+
if key.is_a? Hash
|
36
|
+
results = Config::ObjectList.new
|
37
|
+
key.each do |field, match_value|
|
38
|
+
field = field.is_a?(Symbol) ? field.to_s : field
|
39
|
+
match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
|
40
|
+
if match_value.is_a?(String) && match_value =~ /^!/
|
41
|
+
operator = :not_equal
|
42
|
+
match_value = match_value.sub(/^!/, '')
|
43
|
+
else
|
44
|
+
operator = :equal
|
45
|
+
end
|
46
|
+
each do |name, config|
|
47
|
+
value = config[field]
|
48
|
+
if value.is_a? Array
|
49
|
+
if value.include?(match_value)
|
50
|
+
results[name] = config
|
51
|
+
end
|
52
|
+
else
|
53
|
+
if operator == :equal && value == match_value
|
54
|
+
results[name] = config
|
55
|
+
elsif operator == :not_equal && value != match_value
|
56
|
+
results[name] = config
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
results
|
62
|
+
else
|
63
|
+
super key.to_s
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def exclude(node)
|
68
|
+
list = self.dup
|
69
|
+
list.delete(node.name)
|
70
|
+
return list
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_node(&block)
|
74
|
+
self.keys.sort.each do |node_name|
|
75
|
+
yield self[node_name]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# def <<(object)
|
80
|
+
# if object.is_a? Config::ObjectList
|
81
|
+
# self.merge!(object)
|
82
|
+
# elsif object['name']
|
83
|
+
# self[object['name']] = object
|
84
|
+
# else
|
85
|
+
# raise ArgumentError.new('argument must be a Config::Object or a Config::ObjectList')
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
|
89
|
+
def add(name, object)
|
90
|
+
self[name] = object
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# converts the hash of configs into an array of hashes, with ONLY the specified fields
|
95
|
+
#
|
96
|
+
def fields(*fields)
|
97
|
+
result = []
|
98
|
+
keys.sort.each do |name|
|
99
|
+
result << self[name].pick(*fields)
|
100
|
+
end
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# like fields(), but returns an array of values instead of an array of hashes.
|
106
|
+
#
|
107
|
+
def field(field)
|
108
|
+
field = field.to_s
|
109
|
+
result = []
|
110
|
+
keys.sort.each do |name|
|
111
|
+
result << self[name].get(field)
|
112
|
+
end
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# pick_fields(field1, field2, ...)
|
118
|
+
#
|
119
|
+
# generates a Hash from the object list, but with only the fields that are picked.
|
120
|
+
#
|
121
|
+
# If there are more than one field, then the result is a Hash of Hashes.
|
122
|
+
# If there is just one field, it is a simple map to the value.
|
123
|
+
#
|
124
|
+
# For example:
|
125
|
+
#
|
126
|
+
# "neighbors" = "= nodes_like_me[:services => :couchdb].pick_fields('domain.full', 'ip_address')"
|
127
|
+
#
|
128
|
+
# generates this:
|
129
|
+
#
|
130
|
+
# neighbors:
|
131
|
+
# couch1:
|
132
|
+
# domain_full: couch1.bitmask.net
|
133
|
+
# ip_address: "10.5.5.44"
|
134
|
+
# couch2:
|
135
|
+
# domain_full: couch2.bitmask.net
|
136
|
+
# ip_address: "10.5.5.52"
|
137
|
+
#
|
138
|
+
# But this:
|
139
|
+
#
|
140
|
+
# "neighbors": "= nodes_like_me[:services => :couchdb].pick_fields('domain.full')"
|
141
|
+
#
|
142
|
+
# will generate this:
|
143
|
+
#
|
144
|
+
# neighbors:
|
145
|
+
# couch1: couch1.bitmask.net
|
146
|
+
# couch2: couch2.bitmask.net
|
147
|
+
#
|
148
|
+
def pick_fields(*fields)
|
149
|
+
self.values.inject({}) do |hsh, node|
|
150
|
+
value = self[node.name].pick(*fields)
|
151
|
+
if fields.size == 1
|
152
|
+
value = value.values.first
|
153
|
+
end
|
154
|
+
hsh[node.name] = value
|
155
|
+
hsh
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# applies inherit_from! to all objects.
|
161
|
+
#
|
162
|
+
def inherit_from!(object_list)
|
163
|
+
object_list.each do |name, object|
|
164
|
+
if self[name]
|
165
|
+
self[name].inherit_from!(object)
|
166
|
+
else
|
167
|
+
self[name] = object.dup
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# A class for the secrets.json file
|
4
|
+
#
|
5
|
+
#
|
6
|
+
|
7
|
+
module LeapCli; module Config
|
8
|
+
|
9
|
+
class Secrets < Object
|
10
|
+
attr_reader :node_list
|
11
|
+
|
12
|
+
def initialize(manager=nil)
|
13
|
+
super(manager)
|
14
|
+
@discovered_keys = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def set(key, value)
|
18
|
+
key = key.to_s
|
19
|
+
@discovered_keys[key] = true
|
20
|
+
self[key] ||= value
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# if only_discovered_keys is true, then we will only export
|
25
|
+
# those secrets that have been discovered and the prior ones will be cleaned out.
|
26
|
+
#
|
27
|
+
# this should only be triggered when all nodes have been processed, otherwise
|
28
|
+
# secrets that are actually in use will get mistakenly removed.
|
29
|
+
#
|
30
|
+
#
|
31
|
+
def dump_json(only_discovered_keys=false)
|
32
|
+
if only_discovered_keys
|
33
|
+
self.each_key do |key|
|
34
|
+
unless @discovered_keys[key]
|
35
|
+
self.delete(key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
super()
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end; end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# A class for node services or node tags.
|
4
|
+
#
|
5
|
+
#
|
6
|
+
|
7
|
+
module LeapCli; module Config
|
8
|
+
|
9
|
+
class Tag < Object
|
10
|
+
attr_reader :node_list
|
11
|
+
|
12
|
+
def initialize(manager=nil)
|
13
|
+
super(manager)
|
14
|
+
@node_list = Config::ObjectList.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end; end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#
|
2
|
+
# The Leapfile is the bootstrap configuration file for a LEAP provider.
|
3
|
+
#
|
4
|
+
# It is akin to a Gemfile, Rakefile, or Capfile (e.g. it is a ruby file that gets eval'ed)
|
5
|
+
#
|
6
|
+
|
7
|
+
module LeapCli
|
8
|
+
def self.leapfile
|
9
|
+
@leapfile ||= Leapfile.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Leapfile
|
13
|
+
attr_accessor :platform_directory_path
|
14
|
+
attr_accessor :provider_directory_path
|
15
|
+
attr_accessor :custom_vagrant_vm_line
|
16
|
+
attr_accessor :leap_version
|
17
|
+
attr_accessor :log
|
18
|
+
attr_accessor :vagrant_network
|
19
|
+
attr_accessor :platform_branch
|
20
|
+
attr_accessor :allow_production_deploy
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@vagrant_network = '10.5.5.0/24'
|
24
|
+
end
|
25
|
+
|
26
|
+
def load(search_directory=nil)
|
27
|
+
directory = File.expand_path(find_in_directory_tree('Leapfile', search_directory))
|
28
|
+
if directory == '/'
|
29
|
+
return nil
|
30
|
+
else
|
31
|
+
#
|
32
|
+
# set up paths
|
33
|
+
#
|
34
|
+
@provider_directory_path = directory
|
35
|
+
read_settings(directory + '/Leapfile')
|
36
|
+
read_settings(ENV['HOME'] + '/.leaprc')
|
37
|
+
@platform_directory_path = File.expand_path(@platform_directory_path || '../leap_platform', @provider_directory_path)
|
38
|
+
|
39
|
+
#
|
40
|
+
# load the platform
|
41
|
+
#
|
42
|
+
require "#{@platform_directory_path}/platform.rb"
|
43
|
+
if !Leap::Platform.compatible_with_cli?(LeapCli::VERSION)
|
44
|
+
Util.bail! "This leap command (v#{LeapCli::VERSION}) " +
|
45
|
+
"is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}). " +
|
46
|
+
"You need leap command #{Leap::Platform.compatible_cli.first} to #{Leap::Platform.compatible_cli.last}."
|
47
|
+
end
|
48
|
+
if !Leap::Platform.version_in_range?(LeapCli::COMPATIBLE_PLATFORM_VERSION)
|
49
|
+
Util.bail! "This leap command (v#{LeapCli::VERSION}) " +
|
50
|
+
"is not compatible with the platform #{@platform_directory_path} (v#{Leap::Platform.version}). " +
|
51
|
+
"You need platform version #{LeapCli::COMPATIBLE_PLATFORM_VERSION.first} to #{LeapCli::COMPATIBLE_PLATFORM_VERSION.last}."
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# set defaults
|
56
|
+
#
|
57
|
+
if @allow_production_deploy.nil?
|
58
|
+
# by default, only allow production deploys from 'master' or if not a git repo
|
59
|
+
@allow_production_deploy = !LeapCli::Util.is_git_directory?(@provider_directory_path) ||
|
60
|
+
LeapCli::Util.current_git_branch(@provider_directory_path) == 'master'
|
61
|
+
end
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def read_settings(file)
|
69
|
+
if File.exists? file
|
70
|
+
Util::log 2, :read, file
|
71
|
+
instance_eval(File.read(file), file)
|
72
|
+
validate(file)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_in_directory_tree(filename, directory_tree=nil)
|
77
|
+
search_dir = directory_tree || Dir.pwd
|
78
|
+
while search_dir != "/"
|
79
|
+
Dir.foreach(search_dir) do |f|
|
80
|
+
return search_dir if f == filename
|
81
|
+
end
|
82
|
+
search_dir = File.dirname(search_dir)
|
83
|
+
end
|
84
|
+
return search_dir
|
85
|
+
end
|
86
|
+
|
87
|
+
PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/
|
88
|
+
|
89
|
+
def validate(file)
|
90
|
+
Util::assert! vagrant_network =~ PRIVATE_IP_RANGES do
|
91
|
+
Util::log 0, :error, "in #{file}: vagrant_network is not a local private network"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Bundle and rubygems each have their own way of modifying $LOAD_PATH.
|
3
|
+
#
|
4
|
+
# We want to make sure that the right paths are loaded, including the
|
5
|
+
# vendored gems, regardless of how leap is run.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
|
9
|
+
require File.expand_path('../version', __FILE__)
|
10
|
+
|
11
|
+
base_leap_dir = File.expand_path('../../..', __FILE__)
|
12
|
+
LeapCli::LOAD_PATHS.each do |path|
|
13
|
+
path = File.expand_path(path, base_leap_dir)
|
14
|
+
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
15
|
+
end
|