leap_cli 1.5.1 → 1.5.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|