leap_cli 1.5.1 → 1.5.6
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/lib/leap_cli.rb +1 -0
- data/lib/leap_cli/commands/compile.rb +1 -1
- data/lib/leap_cli/commands/db.rb +20 -0
- data/lib/leap_cli/commands/deploy.rb +2 -2
- data/lib/leap_cli/commands/list.rb +17 -10
- data/lib/leap_cli/commands/new.rb +21 -5
- data/lib/leap_cli/commands/node.rb +29 -15
- data/lib/leap_cli/commands/test.rb +1 -1
- data/lib/leap_cli/commands/vagrant.rb +2 -2
- data/lib/leap_cli/config/macros.rb +29 -5
- data/lib/leap_cli/config/manager.rb +104 -55
- data/lib/leap_cli/config/object.rb +5 -3
- data/lib/leap_cli/config/object_list.rb +8 -1
- data/lib/leap_cli/config/secrets.rb +1 -0
- data/lib/leap_cli/exceptions.rb +11 -0
- data/lib/leap_cli/log.rb +1 -0
- data/lib/leap_cli/remote/tasks.rb +31 -16
- data/lib/leap_cli/util.rb +4 -4
- data/lib/leap_cli/util/remote_command.rb +29 -5
- data/lib/leap_cli/util/secret.rb +1 -0
- data/lib/leap_cli/version.rb +2 -2
- metadata +22 -4
data/lib/leap_cli.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module LeapCli; module Commands
|
2
|
+
|
3
|
+
desc 'Database commands.'
|
4
|
+
command :db do |db|
|
5
|
+
db.desc 'Destroy all the databases.'
|
6
|
+
db.command :destroy do |destroy|
|
7
|
+
destroy.action do |global_options,options,args|
|
8
|
+
say 'You are about to permanently destroy all database data.'
|
9
|
+
return unless agree("Continue? ")
|
10
|
+
nodes = manager.nodes[:services => 'couchdb']
|
11
|
+
ssh_connect(nodes, connect_options(options)) do |ssh|
|
12
|
+
ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "db destroyed" || echo "db already destroyed"')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
end; end
|
@@ -38,7 +38,7 @@ module LeapCli
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
compile_hiera_files
|
41
|
+
compile_hiera_files
|
42
42
|
|
43
43
|
ssh_connect(nodes, connect_options(options)) do |ssh|
|
44
44
|
ssh.leap.log :checking, 'node' do
|
@@ -54,7 +54,7 @@ module LeapCli
|
|
54
54
|
end
|
55
55
|
unless options[:sync]
|
56
56
|
ssh.leap.log :applying, "puppet" do
|
57
|
-
ssh.puppet.apply(:verbosity => LeapCli.log_level, :tags => tags(options), :force => options[:force])
|
57
|
+
ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min, :tags => tags(options), :force => options[:force])
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -15,6 +15,11 @@ module LeapCli; module Commands
|
|
15
15
|
c.flag 'print', :desc => 'What attributes to print (optional)'
|
16
16
|
c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
|
17
17
|
c.action do |global_options,options,args|
|
18
|
+
if global_options[:color]
|
19
|
+
colors = ['cyan', 'white']
|
20
|
+
else
|
21
|
+
colors = [nil, nil]
|
22
|
+
end
|
18
23
|
puts
|
19
24
|
if options['disabled']
|
20
25
|
manager.load(:include_disabled => true) # reload, with disabled nodes
|
@@ -23,11 +28,11 @@ module LeapCli; module Commands
|
|
23
28
|
print_node_properties(manager.filter(args), options['print'])
|
24
29
|
else
|
25
30
|
if args.any?
|
26
|
-
NodeTable.new(manager.filter(args)).run
|
31
|
+
NodeTable.new(manager.filter(args), colors).run
|
27
32
|
else
|
28
|
-
TagTable.new('SERVICES', manager.services).run
|
29
|
-
TagTable.new('TAGS', manager.tags).run
|
30
|
-
NodeTable.new(manager.nodes).run
|
33
|
+
TagTable.new('SERVICES', manager.services, colors).run
|
34
|
+
TagTable.new('TAGS', manager.tags, colors).run
|
35
|
+
NodeTable.new(manager.nodes, colors).run
|
31
36
|
end
|
32
37
|
end
|
33
38
|
end
|
@@ -57,20 +62,21 @@ module LeapCli; module Commands
|
|
57
62
|
|
58
63
|
class TagTable
|
59
64
|
include CommandLineReporter
|
60
|
-
def initialize(heading, tag_list)
|
65
|
+
def initialize(heading, tag_list, colors)
|
61
66
|
@heading = heading
|
62
67
|
@tag_list = tag_list
|
68
|
+
@colors = colors
|
63
69
|
end
|
64
70
|
def run
|
65
71
|
tags = @tag_list.keys.sort
|
66
72
|
max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
|
67
73
|
table :border => false do
|
68
|
-
row :
|
74
|
+
row :color => @colors[0] do
|
69
75
|
column @heading, :align => 'right', :width => max_width
|
70
76
|
column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
|
71
77
|
end
|
72
78
|
tags.each do |tag|
|
73
|
-
row do
|
79
|
+
row :color => @colors[1] do
|
74
80
|
column tag
|
75
81
|
column @tag_list[tag].node_list.keys.sort.join(', ')
|
76
82
|
end
|
@@ -85,8 +91,9 @@ module LeapCli; module Commands
|
|
85
91
|
#
|
86
92
|
class NodeTable
|
87
93
|
include CommandLineReporter
|
88
|
-
def initialize(node_list)
|
94
|
+
def initialize(node_list, colors)
|
89
95
|
@node_list = node_list
|
96
|
+
@colors = colors
|
90
97
|
end
|
91
98
|
def run
|
92
99
|
rows = @node_list.keys.sort.collect do |node_name|
|
@@ -102,13 +109,13 @@ module LeapCli; module Commands
|
|
102
109
|
max_service_width = (rows.map{|i|i[1]} + ["SERVICES"]).inject(0) {|max,i| [i.size+padding+padding,max].max}
|
103
110
|
max_tag_width = (rows.map{|i|i[2]} + ["TAGS"] ).inject(0) {|max,i| [i.size,max].max}
|
104
111
|
table :border => false do
|
105
|
-
row :
|
112
|
+
row :color => @colors[0] do
|
106
113
|
column "NODES", :align => 'right', :width => max_node_width
|
107
114
|
column "SERVICES", :width => max_service_width, :padding => 2
|
108
115
|
column "TAGS", :width => max_tag_width
|
109
116
|
end
|
110
117
|
rows.each do |r|
|
111
|
-
row do
|
118
|
+
row :color => @colors[1] do
|
112
119
|
column r[0]
|
113
120
|
column r[1]
|
114
121
|
column r[2]
|
@@ -14,10 +14,11 @@ module LeapCli; module Commands
|
|
14
14
|
c.action do |global, options, args|
|
15
15
|
directory = File.expand_path(args.first)
|
16
16
|
create_provider_directory(global, directory)
|
17
|
-
options[:domain] ||=
|
18
|
-
options[:name] ||=
|
19
|
-
options[:platform] ||=
|
20
|
-
options[:
|
17
|
+
options[:domain] ||= ask_string("The primary domain of the provider: ") {|q| q.default = 'example.org'}
|
18
|
+
options[:name] ||= ask_string("The name of the provider: ") {|q| q.default = 'Example'}
|
19
|
+
options[:platform] ||= ask_string("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)}
|
20
|
+
options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\//
|
21
|
+
options[:contacts] ||= ask_string("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]}
|
21
22
|
options[:platform] = relative_path(options[:platform])
|
22
23
|
create_initial_provider_files(directory, global, options)
|
23
24
|
end
|
@@ -27,6 +28,21 @@ module LeapCli; module Commands
|
|
27
28
|
|
28
29
|
DEFAULT_REPO = 'https://leap.se/git/leap_platform.git'
|
29
30
|
|
31
|
+
#
|
32
|
+
# don't let the user specify any of the following: y, yes, n, no
|
33
|
+
# they must actually input a real string
|
34
|
+
#
|
35
|
+
def ask_string(str, &block)
|
36
|
+
while true
|
37
|
+
value = ask(str, &block)
|
38
|
+
if value =~ /^(y|yes|n|no)$/i
|
39
|
+
say "`#{value}` is not a valid value. Try again"
|
40
|
+
else
|
41
|
+
return value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
30
46
|
#
|
31
47
|
# creates a new provider directory
|
32
48
|
#
|
@@ -77,7 +93,7 @@ module LeapCli; module Commands
|
|
77
93
|
end
|
78
94
|
|
79
95
|
def relative_path(path)
|
80
|
-
Pathname.new(path).relative_path_from(Pathname.new(Path.provider)).to_s
|
96
|
+
Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(Path.provider)).to_s
|
81
97
|
end
|
82
98
|
|
83
99
|
def leapfile_content(options)
|
@@ -22,8 +22,7 @@ module LeapCli; module Commands
|
|
22
22
|
add.action do |global_options,options,args|
|
23
23
|
# argument sanity checks
|
24
24
|
name = args.first
|
25
|
-
|
26
|
-
assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}'"
|
25
|
+
assert_valid_node_name!(name, options[:local])
|
27
26
|
assert_files_missing! [:node_config, name]
|
28
27
|
|
29
28
|
# create and seed new node
|
@@ -33,12 +32,14 @@ module LeapCli; module Commands
|
|
33
32
|
end
|
34
33
|
seed_node_data(node, args[1..-1])
|
35
34
|
validate_ip_address(node)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
begin
|
36
|
+
write_file! [:node_config, name], node.dump_json + "\n"
|
37
|
+
node['name'] = name
|
38
|
+
if file_exists? :ca_cert, :ca_key
|
39
|
+
generate_cert_for_node(manager.reload_node!(node))
|
40
|
+
end
|
41
|
+
rescue LeapCli::ConfigError => exc
|
42
|
+
remove_node_files(name)
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
@@ -64,10 +65,9 @@ module LeapCli; module Commands
|
|
64
65
|
ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
|
65
66
|
ssh_connect(node, ssh_connect_options) do |ssh|
|
66
67
|
if node.vagrant?
|
67
|
-
ssh.
|
68
|
-
else
|
69
|
-
ssh.install_authorized_keys
|
68
|
+
ssh.install_insecure_vagrant_key
|
70
69
|
end
|
70
|
+
ssh.install_authorized_keys
|
71
71
|
ssh.install_prerequisites
|
72
72
|
ssh.leap.capture(facter_cmd) do |response|
|
73
73
|
if response[:exitcode] == 0
|
@@ -89,6 +89,7 @@ module LeapCli; module Commands
|
|
89
89
|
mv.action do |global_options,options,args|
|
90
90
|
node = get_node_from_args(args)
|
91
91
|
new_name = args.last
|
92
|
+
assert_valid_node_name!(new_name, node.vagrant?)
|
92
93
|
ensure_dir [:node_files_dir, new_name]
|
93
94
|
Leap::Platform.node_files.each do |path|
|
94
95
|
rename_file! [path, node.name], [path, new_name]
|
@@ -103,9 +104,7 @@ module LeapCli; module Commands
|
|
103
104
|
node.command :rm do |rm|
|
104
105
|
rm.action do |global_options,options,args|
|
105
106
|
node = get_node_from_args(args)
|
106
|
-
(
|
107
|
-
remove_file! [path, node.name]
|
108
|
-
end
|
107
|
+
remove_node_files(node.name)
|
109
108
|
if node.vagrant?
|
110
109
|
vagrant_command("destroy --force", [node.name])
|
111
110
|
end
|
@@ -237,8 +236,14 @@ module LeapCli; module Commands
|
|
237
236
|
end
|
238
237
|
end
|
239
238
|
|
239
|
+
def remove_node_files(node_name)
|
240
|
+
(Leap::Platform.node_files + [:node_files_dir]).each do |path|
|
241
|
+
remove_file! [path, node_name]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
240
245
|
#
|
241
|
-
#
|
246
|
+
# conversions:
|
242
247
|
#
|
243
248
|
# "x,y,z" => ["x","y","z"]
|
244
249
|
#
|
@@ -273,4 +278,13 @@ module LeapCli; module Commands
|
|
273
278
|
end
|
274
279
|
end
|
275
280
|
|
281
|
+
def assert_valid_node_name!(name, local=false)
|
282
|
+
assert! name, 'No <node-name> specified.'
|
283
|
+
if local
|
284
|
+
assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
|
285
|
+
else
|
286
|
+
assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
276
290
|
end; end
|
@@ -46,7 +46,7 @@ module LeapCli; module Commands
|
|
46
46
|
assert_config! 'provider.ca.client_certificates.unlimited_prefix'
|
47
47
|
assert_config! 'provider.ca.client_certificates.limited_prefix'
|
48
48
|
template = read_file! Path.find_file(:test_client_openvpn_template)
|
49
|
-
manager.
|
49
|
+
manager.environment_names.each do |env|
|
50
50
|
vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_limited' => true]
|
51
51
|
if vpn_nodes.any?
|
52
52
|
generate_test_client_cert(provider.ca.client_certificates.limited_prefix) do |key, cert|
|
@@ -156,7 +156,7 @@ module LeapCli; module Commands
|
|
156
156
|
if node.vagrant?
|
157
157
|
lines << %[ config.vm.define :#{node.name} do |config|]
|
158
158
|
lines << %[ config.vm.box = "leap-wheezy"]
|
159
|
-
lines << %[ config.vm.box_url = "https://downloads.leap.se/leap-debian.box"]
|
159
|
+
lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.box"]
|
160
160
|
lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"]
|
161
161
|
lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
162
162
|
lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]]
|
@@ -170,7 +170,7 @@ module LeapCli; module Commands
|
|
170
170
|
if node.vagrant?
|
171
171
|
lines << %[ config.vm.define :#{node.name} do |config|]
|
172
172
|
lines << %[ config.vm.box = "leap-wheezy"]
|
173
|
-
lines << %[ config.vm.box_url = "https://downloads.leap.se/leap-debian.box"]
|
173
|
+
lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.box"]
|
174
174
|
lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
|
175
175
|
lines << %[ config.vm.provider "virtualbox" do |v|]
|
176
176
|
lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
#
|
2
3
|
# MACROS
|
3
4
|
# these are methods available when eval'ing a value in the .json configuration
|
@@ -5,6 +6,8 @@
|
|
5
6
|
# This module is included in Config::Object
|
6
7
|
#
|
7
8
|
|
9
|
+
require 'base32'
|
10
|
+
|
8
11
|
module LeapCli; module Config
|
9
12
|
module Macros
|
10
13
|
##
|
@@ -22,7 +25,7 @@ module LeapCli; module Config
|
|
22
25
|
# grab an environment appropriate provider
|
23
26
|
#
|
24
27
|
def provider
|
25
|
-
global.
|
28
|
+
global.env(@node.environment).provider
|
26
29
|
end
|
27
30
|
|
28
31
|
#
|
@@ -60,9 +63,9 @@ module LeapCli; module Config
|
|
60
63
|
filepath = Path.find_file(filename)
|
61
64
|
if filepath
|
62
65
|
if filepath =~ /\.erb$/
|
63
|
-
ERB.new(File.read(filepath), nil, '%<>').result(binding)
|
66
|
+
ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
|
64
67
|
else
|
65
|
-
File.read(filepath)
|
68
|
+
File.read(filepath, :encoding => 'UTF-8')
|
66
69
|
end
|
67
70
|
else
|
68
71
|
raise FileMissing.new(Path.named_path(filename), options)
|
@@ -129,6 +132,16 @@ module LeapCli; module Config
|
|
129
132
|
@manager.secrets.set(name, Util::Secret.generate(length), @node[:environment])
|
130
133
|
end
|
131
134
|
|
135
|
+
# inserts a base32 encoded secret
|
136
|
+
def base32_secret(name, length=20)
|
137
|
+
@manager.secrets.set(name, Base32.encode(Util::Secret.generate(length)), @node[:environment])
|
138
|
+
end
|
139
|
+
|
140
|
+
# Picks a random obfsproxy port from given range
|
141
|
+
def rand_range(name, range)
|
142
|
+
@manager.secrets.set(name, rand(range), @node[:environment])
|
143
|
+
end
|
144
|
+
|
132
145
|
#
|
133
146
|
# inserts an hexidecimal secret string, generating it if needed.
|
134
147
|
#
|
@@ -343,14 +356,14 @@ module LeapCli; module Config
|
|
343
356
|
hash = {}
|
344
357
|
keys = Dir.glob(Path.named_path([:user_ssh, '*']))
|
345
358
|
keys.sort.each do |keyfile|
|
346
|
-
ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
|
359
|
+
ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
|
347
360
|
name = File.basename(File.dirname(keyfile))
|
348
361
|
hash[name] = {
|
349
362
|
"type" => ssh_type,
|
350
363
|
"key" => ssh_key
|
351
364
|
}
|
352
365
|
end
|
353
|
-
ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key)).strip.split(" ")
|
366
|
+
ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
|
354
367
|
hash[Leap::Platform.monitor_username] = {
|
355
368
|
"type" => ssh_type,
|
356
369
|
"key" => ssh_key
|
@@ -402,5 +415,16 @@ module LeapCli; module Config
|
|
402
415
|
end
|
403
416
|
end
|
404
417
|
|
418
|
+
#
|
419
|
+
# wrap something that might fail in `try`. e.g.
|
420
|
+
#
|
421
|
+
# "= try{ nodes[:services => 'tor'].first.ip_address } "
|
422
|
+
#
|
423
|
+
def try(&block)
|
424
|
+
yield
|
425
|
+
rescue NoMethodError
|
426
|
+
nil
|
427
|
+
end
|
428
|
+
|
405
429
|
end
|
406
430
|
end; end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'json/pure'
|
2
4
|
|
3
5
|
if $ruby_version < [1,9]
|
@@ -7,16 +9,24 @@ end
|
|
7
9
|
module LeapCli
|
8
10
|
module Config
|
9
11
|
|
12
|
+
class Environment
|
13
|
+
attr_accessor :services, :tags, :provider
|
14
|
+
end
|
15
|
+
|
10
16
|
#
|
11
17
|
# A class to manage all the objects in all the configuration files.
|
12
18
|
#
|
13
19
|
class Manager
|
14
20
|
|
21
|
+
def initialize
|
22
|
+
@environments = {} # hash of `Environment` objects, keyed by name.
|
23
|
+
end
|
24
|
+
|
15
25
|
##
|
16
26
|
## ATTRIBUTES
|
17
27
|
##
|
18
28
|
|
19
|
-
attr_reader :
|
29
|
+
attr_reader :nodes, :common, :secrets
|
20
30
|
attr_reader :base_services, :base_tags, :base_provider, :base_common
|
21
31
|
|
22
32
|
#
|
@@ -30,10 +40,24 @@ module LeapCli
|
|
30
40
|
# returns an Array of all the environments defined for this provider.
|
31
41
|
# the returned array includes nil (for the default environment)
|
32
42
|
#
|
33
|
-
def
|
34
|
-
@
|
43
|
+
def environment_names
|
44
|
+
@environment_names ||= [nil] + env.tags.collect {|name, tag| tag['environment']}.compact
|
35
45
|
end
|
36
46
|
|
47
|
+
#
|
48
|
+
# Returns the appropriate environment variable
|
49
|
+
#
|
50
|
+
def env(env=nil)
|
51
|
+
env ||= 'default'
|
52
|
+
e = @environments[env] ||= Environment.new
|
53
|
+
yield e if block_given?
|
54
|
+
e
|
55
|
+
end
|
56
|
+
|
57
|
+
def services; env('default').services; end
|
58
|
+
def tags; env('default').tags; end
|
59
|
+
def provider; env('default').provider; end
|
60
|
+
|
37
61
|
##
|
38
62
|
## IMPORT EXPORT
|
39
63
|
##
|
@@ -46,34 +70,43 @@ module LeapCli
|
|
46
70
|
|
47
71
|
# load base
|
48
72
|
@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, '*'],
|
50
|
-
@base_common = load_json(Path.named_path(:common_config,
|
51
|
-
@base_provider = load_json(Path.named_path(:provider_config,
|
73
|
+
@base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag)
|
74
|
+
@base_common = load_json( Path.named_path(:common_config, Path.provider_base), Config::Object)
|
75
|
+
@base_provider = load_json( Path.named_path(:provider_config, Path.provider_base), Config::Provider)
|
52
76
|
|
53
77
|
# load provider
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@
|
68
|
-
|
78
|
+
@nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node)
|
79
|
+
@common = load_json( Path.named_path(:common_config, @provider_dir), Config::Object)
|
80
|
+
@secrets = load_json( Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
|
81
|
+
@common.inherit_from! @base_common
|
82
|
+
|
83
|
+
# load provider services, tags, and provider.json, DEFAULT environment
|
84
|
+
log 3, :loading, 'default environment.........'
|
85
|
+
env('default') do |e|
|
86
|
+
e.services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
|
87
|
+
e.tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
|
88
|
+
e.provider = load_json( Path.named_path(:provider_config, @provider_dir), Config::Provider, :assert => true)
|
89
|
+
e.services.inherit_from! @base_services
|
90
|
+
e.tags.inherit_from! @base_tags
|
91
|
+
e.provider.inherit_from! @base_provider
|
92
|
+
validate_provider(e.provider)
|
93
|
+
end
|
94
|
+
|
95
|
+
# load provider services, tags, and provider.json, OTHER environments
|
96
|
+
environment_names.each do |ename|
|
97
|
+
next unless ename
|
98
|
+
log 3, :loading, '%s environment.........' % ename
|
99
|
+
env(ename) do |e|
|
100
|
+
e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag)
|
101
|
+
e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag)
|
102
|
+
e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider)
|
103
|
+
e.services.inherit_from! env.services
|
104
|
+
e.tags.inherit_from! env.tags
|
105
|
+
e.provider.inherit_from! env.provider
|
106
|
+
validate_provider(e.provider)
|
107
|
+
end
|
69
108
|
end
|
70
|
-
### END HACK
|
71
109
|
|
72
|
-
# inherit
|
73
|
-
@services.inherit_from! base_services
|
74
|
-
@tags.inherit_from! base_tags
|
75
|
-
@common.inherit_from! base_common
|
76
|
-
@provider.inherit_from! base_provider
|
77
110
|
@nodes.each do |name, node|
|
78
111
|
Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
|
79
112
|
@nodes[name] = apply_inheritance(node)
|
@@ -82,19 +115,6 @@ module LeapCli
|
|
82
115
|
unless options[:include_disabled]
|
83
116
|
remove_disabled_nodes
|
84
117
|
end
|
85
|
-
|
86
|
-
# load optional environment specific providers
|
87
|
-
validate_provider(@provider)
|
88
|
-
@providers = {}
|
89
|
-
environments.each do |env|
|
90
|
-
if Path.defined?(:provider_env_config)
|
91
|
-
provider_path = Path.named_path([:provider_env_config, env], @provider_dir)
|
92
|
-
providers[env] = load_json(provider_path, Config::Provider)
|
93
|
-
providers[env].inherit_from! @provider
|
94
|
-
validate_provider(providers[env])
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
118
|
end
|
99
119
|
|
100
120
|
#
|
@@ -202,6 +222,11 @@ module LeapCli
|
|
202
222
|
# returns a single Config::Object that corresponds to a Node.
|
203
223
|
#
|
204
224
|
def node(name)
|
225
|
+
if name =~ /\./
|
226
|
+
# probably got a fqdn, since periods are not allowed in node names.
|
227
|
+
# so, take the part before the first period as the node name
|
228
|
+
name = name.split('.').first
|
229
|
+
end
|
205
230
|
@nodes[name]
|
206
231
|
end
|
207
232
|
|
@@ -219,18 +244,19 @@ module LeapCli
|
|
219
244
|
nodes.each_node &block
|
220
245
|
end
|
221
246
|
|
222
|
-
def reload_node(node)
|
223
|
-
@nodes[node.name] = apply_inheritance(node)
|
247
|
+
def reload_node!(node)
|
248
|
+
@nodes[node.name] = apply_inheritance!(node)
|
224
249
|
end
|
225
250
|
|
226
251
|
private
|
227
252
|
|
228
|
-
def load_all_json(pattern, object_class)
|
253
|
+
def load_all_json(pattern, object_class, options={})
|
229
254
|
results = Config::ObjectList.new
|
230
255
|
Dir.glob(pattern).each do |filename|
|
256
|
+
next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/
|
231
257
|
obj = load_json(filename, object_class)
|
232
258
|
if obj
|
233
|
-
name = File.basename(filename).sub(
|
259
|
+
name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1')
|
234
260
|
obj['name'] ||= name
|
235
261
|
results[name] = obj
|
236
262
|
end
|
@@ -238,7 +264,10 @@ module LeapCli
|
|
238
264
|
results
|
239
265
|
end
|
240
266
|
|
241
|
-
def load_json(filename, object_class)
|
267
|
+
def load_json(filename, object_class, options={})
|
268
|
+
if options[:assert]
|
269
|
+
Util::assert_files_exist!(filename)
|
270
|
+
end
|
242
271
|
if !File.exists?(filename)
|
243
272
|
return object_class.new(self)
|
244
273
|
end
|
@@ -252,7 +281,7 @@ module LeapCli
|
|
252
281
|
# https://www.ietf.org/rfc/rfc4627.txt
|
253
282
|
#
|
254
283
|
buffer = StringIO.new
|
255
|
-
File.open(filename, "rb") do |f|
|
284
|
+
File.open(filename, "rb", :encoding => 'UTF-8') do |f|
|
256
285
|
while (line = f.gets)
|
257
286
|
next if line =~ /^\s*\/\//
|
258
287
|
buffer << line
|
@@ -300,22 +329,36 @@ module LeapCli
|
|
300
329
|
#
|
301
330
|
# makes a node inherit options from appropriate the common, service, and tag json files.
|
302
331
|
#
|
303
|
-
def apply_inheritance(node)
|
332
|
+
def apply_inheritance(node, throw_exceptions=false)
|
304
333
|
new_node = Config::Node.new(self)
|
305
334
|
name = node.name
|
306
335
|
|
336
|
+
# Guess the environment of the node from the tag names.
|
337
|
+
# (Technically, this is wrong: a tag that sets the environment might not be
|
338
|
+
# named the same as the environment. This code assumes that it is).
|
339
|
+
node_env = self.env
|
340
|
+
if node['tags']
|
341
|
+
node['tags'].to_a.each do |tag|
|
342
|
+
if self.environment_names.include?(tag)
|
343
|
+
node_env = self.env(tag)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
307
348
|
# inherit from common
|
308
349
|
new_node.deep_merge!(@common)
|
309
350
|
|
310
351
|
# inherit from services
|
311
352
|
if node['services']
|
312
353
|
node['services'].to_a.each do |node_service|
|
313
|
-
service =
|
354
|
+
service = node_env.services[node_service]
|
314
355
|
if service.nil?
|
315
|
-
|
356
|
+
msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
|
357
|
+
log 0, :error, msg
|
358
|
+
raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
|
316
359
|
else
|
317
360
|
new_node.deep_merge!(service)
|
318
|
-
|
361
|
+
self.services[node_service].node_list.add(name, new_node)
|
319
362
|
end
|
320
363
|
end
|
321
364
|
end
|
@@ -326,12 +369,14 @@ module LeapCli
|
|
326
369
|
end
|
327
370
|
if node['tags']
|
328
371
|
node['tags'].to_a.each do |node_tag|
|
329
|
-
tag =
|
372
|
+
tag = node_env.tags[node_tag]
|
330
373
|
if tag.nil?
|
331
|
-
|
374
|
+
msg = 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
|
375
|
+
log 0, :error, msg
|
376
|
+
raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
|
332
377
|
else
|
333
378
|
new_node.deep_merge!(tag)
|
334
|
-
|
379
|
+
self.tags[node_tag].node_list.add(name, new_node)
|
335
380
|
end
|
336
381
|
end
|
337
382
|
end
|
@@ -341,6 +386,10 @@ module LeapCli
|
|
341
386
|
return new_node
|
342
387
|
end
|
343
388
|
|
389
|
+
def apply_inheritance!(node)
|
390
|
+
apply_inheritance(node, true)
|
391
|
+
end
|
392
|
+
|
344
393
|
def remove_disabled_nodes
|
345
394
|
@disabled_nodes = Config::ObjectList.new
|
346
395
|
@nodes.each do |name, node|
|
@@ -350,12 +399,12 @@ module LeapCli
|
|
350
399
|
@disabled_nodes[name] = node
|
351
400
|
if node['services']
|
352
401
|
node['services'].to_a.each do |node_service|
|
353
|
-
|
402
|
+
self.services[node_service].node_list.delete(node.name)
|
354
403
|
end
|
355
404
|
end
|
356
405
|
if node['tags']
|
357
406
|
node['tags'].to_a.each do |node_tag|
|
358
|
-
|
407
|
+
self.tags[node_tag].node_list.delete(node.name)
|
359
408
|
end
|
360
409
|
end
|
361
410
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
require 'json/pure' # pure ruby implementation is required for our sorted trick to work.
|
3
5
|
|
@@ -96,7 +98,9 @@ module LeapCli
|
|
96
98
|
#
|
97
99
|
def get!(key)
|
98
100
|
key = key.to_s
|
99
|
-
if key
|
101
|
+
if self.has_key?(key)
|
102
|
+
fetch_value(key)
|
103
|
+
elsif key =~ /\./
|
100
104
|
# for keys with with '.' in them, we start from the root object (@node).
|
101
105
|
keys = key.split('.')
|
102
106
|
value = @node.get!(keys.first)
|
@@ -105,8 +109,6 @@ module LeapCli
|
|
105
109
|
else
|
106
110
|
value
|
107
111
|
end
|
108
|
-
elsif self.has_key?(key)
|
109
|
-
fetch_value(key)
|
110
112
|
else
|
111
113
|
raise NoMethodError.new(key, "No method '#{key}' for #{self.class}")
|
112
114
|
end
|
@@ -182,7 +182,14 @@ module LeapCli
|
|
182
182
|
end
|
183
183
|
|
184
184
|
def tsort_each_child(node_name, &block)
|
185
|
-
self[node_name]
|
185
|
+
if self[node_name]
|
186
|
+
self[node_name].test_dependencies.each do |test_me_first|
|
187
|
+
if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies.
|
188
|
+
# not just the ones that pass the node filter.
|
189
|
+
yield(test_me_first)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
186
193
|
end
|
187
194
|
|
188
195
|
def names_in_test_dependency_order
|
data/lib/leap_cli/log.rb
CHANGED
@@ -80,6 +80,7 @@ module LeapCli
|
|
80
80
|
if title
|
81
81
|
prefix_options = case title
|
82
82
|
when :error then ['error', :red, :bold]
|
83
|
+
when :fatal_error then ['fatal error', :red, :bold]
|
83
84
|
when :warning then ['warning:', :yellow, :bold]
|
84
85
|
when :info then ['info', :cyan, :bold]
|
85
86
|
when :updated then ['updated', :cyan, :bold]
|
@@ -13,38 +13,53 @@ task :install_authorized_keys, :max_hosts => MAX_HOSTS do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
#
|
16
|
-
# for vagrant nodes, we
|
17
|
-
#
|
16
|
+
# for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy
|
17
|
+
# will overwrite authorized_keys.
|
18
18
|
#
|
19
|
-
# why?
|
20
|
-
# without it, it might be impossible to re-initialize a node.
|
21
|
-
#
|
22
|
-
# ok, why is that?
|
23
|
-
# when we init a vagrant node, we force it to use the insecure vagrant key, and not the user's keys
|
24
|
-
# (so re-initialization would be impossible if authorized_keys doesn't include insecure key).
|
25
|
-
#
|
26
|
-
# ok, why force the insecure vagrant key in the first place?
|
19
|
+
# why force the insecure vagrant key?
|
27
20
|
# if we don't do this, then first time initialization might fail if the user has many keys
|
28
21
|
# (ssh will bomb out before it gets to the vagrant key).
|
29
22
|
# and it really doesn't make sense to ask users to pin the insecure vagrant key in their
|
30
23
|
# .ssh/config files.
|
31
24
|
#
|
32
|
-
task :
|
33
|
-
leap.log :
|
25
|
+
task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do
|
26
|
+
leap.log :installing, "insecure vagrant key" do
|
34
27
|
leap.mkdirs '/root/.ssh'
|
35
|
-
|
28
|
+
key_file = File.expand_path('../../../vendor/vagrant_ssh_keys/vagrant.pub', File.dirname(__FILE__))
|
29
|
+
upload key_file, '/root/.ssh/authorized_keys2', :mode => '600'
|
36
30
|
end
|
37
31
|
end
|
38
32
|
|
33
|
+
BAD_APT_GET_UPDATE = /(BADSIG|NO_PUBKEY|KEYEXPIRED|REVKEYSIG|NODATA)/
|
34
|
+
|
39
35
|
task :install_prerequisites, :max_hosts => MAX_HOSTS do
|
36
|
+
apt_get = "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold"
|
40
37
|
leap.mkdirs LeapCli::PUPPET_DESTINATION
|
38
|
+
run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen"
|
41
39
|
leap.log :updating, "package list" do
|
42
|
-
run "apt-get update"
|
40
|
+
run "apt-get update" do |channel, stream, data|
|
41
|
+
# sadly exitcode is unreliable measure if apt-get update hit a failure.
|
42
|
+
if data =~ BAD_APT_GET_UPDATE
|
43
|
+
LeapCli::Util.bail! do
|
44
|
+
LeapCli::Util.log :fatal_error, "in `apt-get update`: #{data}", :host => channel[:host]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
logger.log(1, data, channel[:host])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
leap.log :updating, "server time" do
|
52
|
+
run "( test -f /etc/init.d/ntp && /etc/init.d/ntp stop ) || true"
|
53
|
+
run "test -f /usr/sbin/ntpdate || #{apt_get} install ntpdate"
|
54
|
+
leap.log :running, "ntpdate..." do
|
55
|
+
run "test -f /usr/sbin/ntpdate && ntpdate 0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org"
|
56
|
+
end
|
57
|
+
run "( test -f /etc/init.d/ntp && /etc/init.d/ntp start ) || true"
|
43
58
|
end
|
44
59
|
leap.log :installing, "required packages" do
|
45
|
-
run "
|
60
|
+
run "#{apt_get} install #{leap.required_packages}"
|
46
61
|
end
|
47
|
-
run "
|
62
|
+
#run "locale-gen"
|
48
63
|
leap.mkdirs("/etc/leap", "/srv/leap")
|
49
64
|
leap.mark_initialized
|
50
65
|
end
|
data/lib/leap_cli/util.rb
CHANGED
@@ -193,13 +193,13 @@ module LeapCli
|
|
193
193
|
def read_file!(filepath)
|
194
194
|
filepath = Path.named_path(filepath)
|
195
195
|
assert_files_exist!(filepath)
|
196
|
-
File.read(filepath)
|
196
|
+
File.read(filepath, :encoding => 'UTF-8')
|
197
197
|
end
|
198
198
|
|
199
199
|
def read_file(filepath)
|
200
200
|
filepath = Path.named_path(filepath)
|
201
201
|
if file_exists?(filepath)
|
202
|
-
File.read(filepath)
|
202
|
+
File.read(filepath, :encoding => 'UTF-8')
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
@@ -219,7 +219,7 @@ module LeapCli
|
|
219
219
|
write_file!(filepath, content)
|
220
220
|
end
|
221
221
|
else
|
222
|
-
File.open(filepath, File::RDWR|File::CREAT, 0600) do |f|
|
222
|
+
File.open(filepath, File::RDWR|File::CREAT, 0600, :encoding => 'UTF-8') do |f|
|
223
223
|
f.flock(File::LOCK_EX)
|
224
224
|
old_content = f.read
|
225
225
|
new_content = yield(old_content)
|
@@ -286,7 +286,7 @@ module LeapCli
|
|
286
286
|
end
|
287
287
|
end
|
288
288
|
|
289
|
-
File.open(filepath, 'w', 0600) do |f|
|
289
|
+
File.open(filepath, 'w', 0600, :encoding => 'UTF-8') do |f|
|
290
290
|
f.write contents
|
291
291
|
end
|
292
292
|
|
@@ -13,7 +13,7 @@ module LeapCli; module Util; module RemoteCommand
|
|
13
13
|
node_list = parse_node_list(nodes)
|
14
14
|
|
15
15
|
cap = new_capistrano
|
16
|
-
cap.logger = LeapCli::Logger.new(:level => LeapCli.log_level)
|
16
|
+
cap.logger = LeapCli::Logger.new(:level => [LeapCli.log_level,3].min)
|
17
17
|
user = options[:user] || 'root'
|
18
18
|
cap.set :user, user
|
19
19
|
cap.set :ssh_options, ssh_options # ssh options common to all nodes
|
@@ -31,7 +31,7 @@ module LeapCli; module Util; module RemoteCommand
|
|
31
31
|
end
|
32
32
|
|
33
33
|
node_list.each do |name, node|
|
34
|
-
cap.server node.
|
34
|
+
cap.server node.domain.full, :dummy_arg, node_options(node, options[:ssh_options])
|
35
35
|
end
|
36
36
|
|
37
37
|
yield cap
|
@@ -48,9 +48,34 @@ module LeapCli; module Util; module RemoteCommand
|
|
48
48
|
#
|
49
49
|
# For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
|
50
50
|
#
|
51
|
+
# Capistrano has some very evil behavior in it's ssh.rb:
|
52
|
+
#
|
53
|
+
# ssh_options = Net::SSH.configuration_for(
|
54
|
+
# server.host, ssh_options.fetch(:config, true)
|
55
|
+
# ).merge(ssh_options)
|
56
|
+
# # Once we've loaded the config, we don't need Net::SSH to do it again.
|
57
|
+
# ssh_options[:config] = false
|
58
|
+
#
|
59
|
+
# Net:SSH is supposed to call Net::SSH.configuration_for, but Capistrano is doing it
|
60
|
+
# in advance and then disabling loading of configs.
|
61
|
+
#
|
62
|
+
# The result of this is the following: if you have IdentityFile in your ~/.ssh/config
|
63
|
+
# file, then the above code will transform the ssh_options by reading ~/.ssh/config
|
64
|
+
# and adding the keys specified via IdentityFile to ssh_options...
|
65
|
+
# AND IT WILL SET :keys_only TO TRUE.
|
66
|
+
#
|
67
|
+
# The problem is that :keys_only will disable Net:SSH's ability to use ssh-agent.
|
68
|
+
# With :keys_only set to true, it will not consult the ssh-agent at all.
|
69
|
+
#
|
70
|
+
# So nice of capistrano to parse ~/.ssh/config for us, but then add flags to the
|
71
|
+
# ssh_options that prevent's these options from being useful.
|
72
|
+
#
|
73
|
+
# The current hackaround is to force :keys_only to be false. This allows the config
|
74
|
+
# to be read and also allows ssh-agent to still be used.
|
75
|
+
#
|
51
76
|
def ssh_options
|
52
77
|
{
|
53
|
-
:
|
78
|
+
:keys_only => false, # Don't you dare change this.
|
54
79
|
:global_known_hosts_file => path(:known_hosts),
|
55
80
|
:user_known_hosts_file => '/dev/null',
|
56
81
|
:paranoid => true
|
@@ -67,13 +92,12 @@ module LeapCli; module Util; module RemoteCommand
|
|
67
92
|
# return {:password => password_proc}
|
68
93
|
#
|
69
94
|
def node_options(node, ssh_options_override=nil)
|
70
|
-
ssh_options_override ||= {}
|
71
95
|
{
|
72
96
|
:ssh_options => {
|
73
97
|
# :host_key_alias => node.name, << incompatible with ports in known_hosts
|
74
98
|
:host_name => node.ip_address,
|
75
99
|
:port => node.ssh.port
|
76
|
-
}.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override)
|
100
|
+
}.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override||{})
|
77
101
|
}
|
78
102
|
end
|
79
103
|
|
data/lib/leap_cli/util/secret.rb
CHANGED
data/lib/leap_cli/version.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module LeapCli
|
2
2
|
unless defined?(LeapCli::VERSION)
|
3
|
-
VERSION = '1.5.
|
4
|
-
COMPATIBLE_PLATFORM_VERSION = '0.
|
3
|
+
VERSION = '1.5.6'
|
4
|
+
COMPATIBLE_PLATFORM_VERSION = '0.5.2'..'1.99'
|
5
5
|
SUMMARY = 'Command line interface to the LEAP platform'
|
6
6
|
DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
|
7
7
|
LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib']
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leap_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-06-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -114,7 +114,7 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 2.
|
117
|
+
version: 2.15.5
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
requirements:
|
123
123
|
- - ~>
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.
|
125
|
+
version: 2.15.5
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: net-ssh
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,6 +203,22 @@ dependencies:
|
|
203
203
|
- - ! '>='
|
204
204
|
- !ruby/object:Gem::Version
|
205
205
|
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: base32
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ! '>='
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
type: :runtime
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
206
222
|
- !ruby/object:Gem::Dependency
|
207
223
|
name: activemodel
|
208
224
|
requirement: !ruby/object:Gem::Requirement
|
@@ -244,6 +260,7 @@ files:
|
|
244
260
|
- lib/leap_cli/commands/ca.rb
|
245
261
|
- lib/leap_cli/commands/pre.rb
|
246
262
|
- lib/leap_cli/commands/compile.rb
|
263
|
+
- lib/leap_cli/commands/db.rb
|
247
264
|
- lib/leap_cli/commands/user.rb
|
248
265
|
- lib/leap_cli/commands/node.rb
|
249
266
|
- lib/leap_cli/commands/clean.rb
|
@@ -265,6 +282,7 @@ files:
|
|
265
282
|
- lib/leap_cli/requirements.rb
|
266
283
|
- lib/leap_cli/path.rb
|
267
284
|
- lib/leap_cli/leapfile.rb
|
285
|
+
- lib/leap_cli/exceptions.rb
|
268
286
|
- lib/leap_cli/log.rb
|
269
287
|
- lib/leap_cli/util.rb
|
270
288
|
- lib/leap_cli/config/manager.rb
|