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,369 @@
|
|
1
|
+
require 'json/pure'
|
2
|
+
|
3
|
+
if $ruby_version < [1,9]
|
4
|
+
require 'iconv'
|
5
|
+
end
|
6
|
+
|
7
|
+
module LeapCli
|
8
|
+
module Config
|
9
|
+
|
10
|
+
#
|
11
|
+
# A class to manage all the objects in all the configuration files.
|
12
|
+
#
|
13
|
+
class Manager
|
14
|
+
|
15
|
+
##
|
16
|
+
## ATTRIBUTES
|
17
|
+
##
|
18
|
+
|
19
|
+
attr_reader :services, :tags, :nodes, :provider, :common, :secrets
|
20
|
+
attr_reader :base_services, :base_tags, :base_provider, :base_common
|
21
|
+
|
22
|
+
#
|
23
|
+
# returns the Hash of the contents of facts.json
|
24
|
+
#
|
25
|
+
def facts
|
26
|
+
@facts ||= JSON.parse(Util.read_file(:facts) || "{}")
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# returns an Array of all the environments defined for this provider.
|
31
|
+
# the returned array includes nil (for the default environment)
|
32
|
+
#
|
33
|
+
def environments
|
34
|
+
@environments ||= [nil] + self.tags.collect {|name, tag| tag['environment']}.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
## IMPORT EXPORT
|
39
|
+
##
|
40
|
+
|
41
|
+
#
|
42
|
+
# load .json configuration files
|
43
|
+
#
|
44
|
+
def load(options = {})
|
45
|
+
@provider_dir = Path.provider
|
46
|
+
|
47
|
+
# load base
|
48
|
+
@base_services = load_all_json(Path.named_path([:service_config, '*'], Path.provider_base), Config::Tag)
|
49
|
+
@base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag)
|
50
|
+
@base_common = load_json(Path.named_path(:common_config, Path.provider_base), Config::Object)
|
51
|
+
@base_provider = load_json(Path.named_path(:provider_config, Path.provider_base), Config::Object)
|
52
|
+
|
53
|
+
# load provider
|
54
|
+
provider_path = Path.named_path(:provider_config, @provider_dir)
|
55
|
+
common_path = Path.named_path(:common_config, @provider_dir)
|
56
|
+
Util::assert_files_exist!(provider_path, common_path)
|
57
|
+
@services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag)
|
58
|
+
@tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag)
|
59
|
+
@nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node)
|
60
|
+
@common = load_json(common_path, Config::Object)
|
61
|
+
@provider = load_json(provider_path, Config::Object)
|
62
|
+
@secrets = load_json(Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
|
63
|
+
|
64
|
+
# inherit
|
65
|
+
@services.inherit_from! base_services
|
66
|
+
@tags.inherit_from! base_tags
|
67
|
+
@common.inherit_from! base_common
|
68
|
+
@provider.inherit_from! base_provider
|
69
|
+
@nodes.each do |name, node|
|
70
|
+
Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
|
71
|
+
@nodes[name] = apply_inheritance(node)
|
72
|
+
end
|
73
|
+
|
74
|
+
unless options[:include_disabled]
|
75
|
+
remove_disabled_nodes
|
76
|
+
end
|
77
|
+
|
78
|
+
# validate
|
79
|
+
validate_provider(@provider)
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# save compiled hiera .yaml files
|
84
|
+
#
|
85
|
+
# if a node_list is specified, only update those .yaml files.
|
86
|
+
# otherwise, update all files, destroying files that are no longer used.
|
87
|
+
#
|
88
|
+
def export_nodes(node_list=nil)
|
89
|
+
updated_hiera = []
|
90
|
+
updated_files = []
|
91
|
+
existing_hiera = nil
|
92
|
+
existing_files = nil
|
93
|
+
|
94
|
+
unless node_list
|
95
|
+
node_list = self.nodes
|
96
|
+
existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir))
|
97
|
+
existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir))
|
98
|
+
end
|
99
|
+
|
100
|
+
node_list.each_node do |node|
|
101
|
+
filepath = Path.named_path([:node_files_dir, node.name], @provider_dir)
|
102
|
+
hierapath = Path.named_path([:hiera, node.name], @provider_dir)
|
103
|
+
Util::write_file!(hierapath, node.dump)
|
104
|
+
updated_files << filepath
|
105
|
+
updated_hiera << hierapath
|
106
|
+
end
|
107
|
+
|
108
|
+
if @disabled_nodes
|
109
|
+
# make disabled nodes appear as if they are still active
|
110
|
+
@disabled_nodes.each_node do |node|
|
111
|
+
updated_files << Path.named_path([:node_files_dir, node.name], @provider_dir)
|
112
|
+
updated_hiera << Path.named_path([:hiera, node.name], @provider_dir)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# remove files that are no longer needed
|
117
|
+
if existing_hiera
|
118
|
+
(existing_hiera - updated_hiera).each do |filepath|
|
119
|
+
Util::remove_file!(filepath)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
if existing_files
|
123
|
+
(existing_files - updated_files).each do |filepath|
|
124
|
+
Util::remove_directory!(filepath)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def export_secrets(clean_unused_secrets = false)
|
130
|
+
if @secrets.any?
|
131
|
+
Util.write_file!([:secrets_config, @provider_dir], @secrets.dump_json(clean_unused_secrets) + "\n")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
## FILTERING
|
137
|
+
##
|
138
|
+
|
139
|
+
#
|
140
|
+
# returns a node list consisting only of nodes that satisfy the filter criteria.
|
141
|
+
#
|
142
|
+
# filter: condition [condition] [condition] [+condition]
|
143
|
+
# condition: [node_name | service_name | tag_name]
|
144
|
+
#
|
145
|
+
# if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
|
146
|
+
#
|
147
|
+
def filter(filters)
|
148
|
+
if filters.empty?
|
149
|
+
return nodes
|
150
|
+
end
|
151
|
+
if filters[0] =~ /^\+/
|
152
|
+
# don't let the first filter have a + prefix
|
153
|
+
filters[0] = filters[0][1..-1]
|
154
|
+
end
|
155
|
+
|
156
|
+
node_list = Config::ObjectList.new
|
157
|
+
filters.each do |filter|
|
158
|
+
if filter =~ /^\+/
|
159
|
+
keep_list = nodes_for_name(filter[1..-1])
|
160
|
+
node_list.delete_if do |name, node|
|
161
|
+
if keep_list[name]
|
162
|
+
false
|
163
|
+
else
|
164
|
+
true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
else
|
168
|
+
node_list.merge!(nodes_for_name(filter))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
return node_list
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# same as filter(), but exits if there is no matching nodes
|
176
|
+
#
|
177
|
+
def filter!(filters)
|
178
|
+
node_list = filter(filters)
|
179
|
+
Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'"
|
180
|
+
return node_list
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
# returns a single Config::Object that corresponds to a Node.
|
185
|
+
#
|
186
|
+
def node(name)
|
187
|
+
@nodes[name]
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# returns a single node that is disabled
|
192
|
+
#
|
193
|
+
def disabled_node(name)
|
194
|
+
@disabled_nodes[name]
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# yields each node, in sorted order
|
199
|
+
#
|
200
|
+
def each_node(&block)
|
201
|
+
nodes.each_node &block
|
202
|
+
end
|
203
|
+
|
204
|
+
def reload_node(node)
|
205
|
+
@nodes[node.name] = apply_inheritance(node)
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def load_all_json(pattern, object_class)
|
211
|
+
results = Config::ObjectList.new
|
212
|
+
Dir.glob(pattern).each do |filename|
|
213
|
+
obj = load_json(filename, object_class)
|
214
|
+
if obj
|
215
|
+
name = File.basename(filename).sub(/\.json$/,'')
|
216
|
+
obj['name'] ||= name
|
217
|
+
results[name] = obj
|
218
|
+
end
|
219
|
+
end
|
220
|
+
results
|
221
|
+
end
|
222
|
+
|
223
|
+
def load_json(filename, object_class)
|
224
|
+
if !File.exists?(filename)
|
225
|
+
return object_class.new(self)
|
226
|
+
end
|
227
|
+
|
228
|
+
log :loading, filename, 3
|
229
|
+
|
230
|
+
#
|
231
|
+
# Read a JSON file, strip out comments.
|
232
|
+
#
|
233
|
+
# UTF8 is the default encoding for JSON, but others are allowed:
|
234
|
+
# https://www.ietf.org/rfc/rfc4627.txt
|
235
|
+
#
|
236
|
+
buffer = StringIO.new
|
237
|
+
File.open(filename, "rb") do |f|
|
238
|
+
while (line = f.gets)
|
239
|
+
next if line =~ /^\s*\/\//
|
240
|
+
buffer << line
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# force UTF-8
|
246
|
+
#
|
247
|
+
if $ruby_version >= [1,9]
|
248
|
+
string = buffer.string.force_encoding('utf-8')
|
249
|
+
else
|
250
|
+
string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string)
|
251
|
+
end
|
252
|
+
|
253
|
+
# parse json
|
254
|
+
begin
|
255
|
+
hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {}
|
256
|
+
rescue SyntaxError, JSON::ParserError => exc
|
257
|
+
log 0, :error, 'in file "%s":' % filename
|
258
|
+
log 0, exc.to_s, :indent => 1
|
259
|
+
return nil
|
260
|
+
end
|
261
|
+
object = object_class.new(self)
|
262
|
+
object.deep_merge!(hash)
|
263
|
+
return object
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# remove all the nesting from a hash.
|
268
|
+
#
|
269
|
+
# def flatten_hash(input = {}, output = {}, options = {})
|
270
|
+
# input.each do |key, value|
|
271
|
+
# key = options[:prefix].nil? ? "#{key}" : "#{options[:prefix]}#{options[:delimiter]||"_"}#{key}"
|
272
|
+
# if value.is_a? Hash
|
273
|
+
# flatten_hash(value, output, :prefix => key, :delimiter => options[:delimiter])
|
274
|
+
# else
|
275
|
+
# output[key] = value
|
276
|
+
# end
|
277
|
+
# end
|
278
|
+
# output.replace(input)
|
279
|
+
# output
|
280
|
+
# end
|
281
|
+
|
282
|
+
#
|
283
|
+
# makes a node inherit options from appropriate the common, service, and tag json files.
|
284
|
+
#
|
285
|
+
def apply_inheritance(node)
|
286
|
+
new_node = Config::Node.new(self)
|
287
|
+
name = node.name
|
288
|
+
|
289
|
+
# inherit from common
|
290
|
+
new_node.deep_merge!(@common)
|
291
|
+
|
292
|
+
# inherit from services
|
293
|
+
if node['services']
|
294
|
+
node['services'].to_a.each do |node_service|
|
295
|
+
service = @services[node_service]
|
296
|
+
if service.nil?
|
297
|
+
log 0, :error, 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
|
298
|
+
else
|
299
|
+
new_node.deep_merge!(service)
|
300
|
+
service.node_list.add(name, new_node)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# inherit from tags
|
306
|
+
if node.vagrant?
|
307
|
+
node['tags'] = (node['tags'] || []).to_a + ['local']
|
308
|
+
end
|
309
|
+
if node['tags']
|
310
|
+
node['tags'].to_a.each do |node_tag|
|
311
|
+
tag = @tags[node_tag]
|
312
|
+
if tag.nil?
|
313
|
+
log 0, :error, 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
|
314
|
+
else
|
315
|
+
new_node.deep_merge!(tag)
|
316
|
+
tag.node_list.add(name, new_node)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# inherit from node
|
322
|
+
new_node.deep_merge!(node)
|
323
|
+
return new_node
|
324
|
+
end
|
325
|
+
|
326
|
+
def remove_disabled_nodes
|
327
|
+
@disabled_nodes = Config::ObjectList.new
|
328
|
+
@nodes.each do |name, node|
|
329
|
+
unless node.enabled
|
330
|
+
log 2, :skipping, "disabled node #{name}."
|
331
|
+
@nodes.delete(name)
|
332
|
+
@disabled_nodes[name] = node
|
333
|
+
if node['services']
|
334
|
+
node['services'].to_a.each do |node_service|
|
335
|
+
@services[node_service].node_list.delete(node.name)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
if node['tags']
|
339
|
+
node['tags'].to_a.each do |node_tag|
|
340
|
+
@tags[node_tag].node_list.delete(node.name)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
#
|
349
|
+
# returns a set of nodes corresponding to a single name, where name could be a node name, service name, or tag name.
|
350
|
+
#
|
351
|
+
def nodes_for_name(name)
|
352
|
+
if node = self.nodes[name]
|
353
|
+
Config::ObjectList.new(node)
|
354
|
+
elsif service = self.services[name]
|
355
|
+
service.node_list
|
356
|
+
elsif tag = self.tags[name]
|
357
|
+
tag.node_list
|
358
|
+
else
|
359
|
+
{}
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def validate_provider(provider)
|
364
|
+
# nothing yet.
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#
|
2
|
+
# Configuration for a 'node' (a server in the provider's infrastructure)
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'ipaddr'
|
6
|
+
|
7
|
+
module LeapCli; module Config
|
8
|
+
|
9
|
+
class Node < Object
|
10
|
+
attr_accessor :file_paths
|
11
|
+
|
12
|
+
def initialize(manager=nil)
|
13
|
+
super(manager)
|
14
|
+
@node = self
|
15
|
+
@file_paths = []
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# returns true if this node has an ip address in the range of the vagrant network
|
20
|
+
#
|
21
|
+
def vagrant?
|
22
|
+
begin
|
23
|
+
vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network
|
24
|
+
rescue ArgumentError => exc
|
25
|
+
Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" }
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
ip_address = IPAddr.new @node.get('ip_address')
|
30
|
+
rescue ArgumentError => exc
|
31
|
+
Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'"
|
32
|
+
end
|
33
|
+
return vagrant_range.include?(ip_address)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end; end
|