leap_cli 1.5.6 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/leap +29 -6
- data/lib/leap/platform.rb +36 -1
- data/lib/leap_cli/commands/ca.rb +97 -20
- data/lib/leap_cli/commands/compile.rb +49 -8
- data/lib/leap_cli/commands/db.rb +13 -4
- data/lib/leap_cli/commands/deploy.rb +138 -29
- data/lib/leap_cli/commands/env.rb +76 -0
- data/lib/leap_cli/commands/facts.rb +10 -3
- data/lib/leap_cli/commands/inspect.rb +2 -2
- data/lib/leap_cli/commands/list.rb +10 -10
- data/lib/leap_cli/commands/node.rb +7 -132
- data/lib/leap_cli/commands/node_init.rb +169 -0
- data/lib/leap_cli/commands/pre.rb +4 -27
- data/lib/leap_cli/commands/ssh.rb +152 -0
- data/lib/leap_cli/commands/test.rb +22 -13
- data/lib/leap_cli/commands/user.rb +12 -4
- data/lib/leap_cli/commands/vagrant.rb +4 -4
- data/lib/leap_cli/config/filter.rb +175 -0
- data/lib/leap_cli/config/manager.rb +130 -61
- data/lib/leap_cli/config/node.rb +32 -0
- data/lib/leap_cli/config/object.rb +69 -44
- data/lib/leap_cli/config/object_list.rb +44 -39
- data/lib/leap_cli/config/secrets.rb +24 -12
- data/lib/leap_cli/config/tag.rb +7 -0
- data/lib/{core_ext → leap_cli/core_ext}/boolean.rb +0 -0
- data/lib/{core_ext → leap_cli/core_ext}/hash.rb +0 -0
- data/lib/{core_ext → leap_cli/core_ext}/json.rb +0 -0
- data/lib/{core_ext → leap_cli/core_ext}/nil.rb +0 -0
- data/lib/{core_ext → leap_cli/core_ext}/string.rb +0 -0
- data/lib/leap_cli/core_ext/yaml.rb +29 -0
- data/lib/leap_cli/exceptions.rb +24 -0
- data/lib/leap_cli/leapfile.rb +60 -10
- data/lib/{lib_ext → leap_cli/lib_ext}/capistrano_connections.rb +0 -0
- data/lib/{lib_ext → leap_cli/lib_ext}/gli.rb +0 -0
- data/lib/leap_cli/log.rb +1 -1
- data/lib/leap_cli/logger.rb +18 -1
- data/lib/leap_cli/markdown_document_listener.rb +1 -1
- data/lib/leap_cli/override/json.rb +11 -0
- data/lib/leap_cli/path.rb +20 -6
- data/lib/leap_cli/remote/leap_plugin.rb +2 -2
- data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
- data/lib/leap_cli/remote/rsync_plugin.rb +1 -1
- data/lib/leap_cli/remote/tasks.rb +1 -1
- data/lib/leap_cli/ssh_key.rb +63 -1
- data/lib/leap_cli/util/remote_command.rb +19 -2
- data/lib/leap_cli/util/secret.rb +1 -1
- data/lib/leap_cli/util/x509.rb +3 -2
- data/lib/leap_cli/util.rb +11 -3
- data/lib/leap_cli/version.rb +2 -2
- data/lib/leap_cli.rb +24 -14
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +85 -29
- data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +5 -0
- data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +406 -41
- data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +0 -34
- data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +6 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +36 -1
- metadata +25 -24
- data/lib/leap_cli/commands/shell.rb +0 -89
- data/lib/leap_cli/config/macros.rb +0 -430
- data/lib/leap_cli/constants.rb +0 -7
- data/lib/leap_cli/requirements.rb +0 -19
- data/lib/lib_ext/markdown_document_listener.rb +0 -122
@@ -5,7 +5,7 @@ module LeapCli
|
|
5
5
|
desc 'Apply recipes to a node or set of nodes.'
|
6
6
|
long_desc 'The FILTER can be the name of a node, service, or tag.'
|
7
7
|
arg_name 'FILTER'
|
8
|
-
command :deploy do |c|
|
8
|
+
command [:deploy, :d] do |c|
|
9
9
|
|
10
10
|
# --fast
|
11
11
|
c.switch :fast, :desc => 'Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.',
|
@@ -17,9 +17,12 @@ module LeapCli
|
|
17
17
|
# --force
|
18
18
|
c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
|
19
19
|
|
20
|
+
# --dev
|
21
|
+
c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false
|
22
|
+
|
20
23
|
# --tags
|
21
24
|
c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).',
|
22
|
-
:
|
25
|
+
:arg_name => 'TAG[,TAG]'
|
23
26
|
|
24
27
|
c.flag :port, :desc => 'Override the default SSH port.',
|
25
28
|
:arg_name => 'PORT'
|
@@ -28,9 +31,12 @@ module LeapCli
|
|
28
31
|
:arg_name => 'IPADDRESS'
|
29
32
|
|
30
33
|
c.action do |global,options,args|
|
31
|
-
init_submodules
|
32
34
|
|
33
|
-
|
35
|
+
if options[:dev] != true
|
36
|
+
init_submodules
|
37
|
+
end
|
38
|
+
|
39
|
+
nodes = manager.filter!(args)
|
34
40
|
if nodes.size > 1
|
35
41
|
say "Deploying to these nodes: #{nodes.keys.join(', ')}"
|
36
42
|
if !global[:yes] && !agree("Continue? ")
|
@@ -38,7 +44,16 @@ module LeapCli
|
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
41
|
-
|
47
|
+
environments = nodes.field('environment').uniq
|
48
|
+
if environments.empty?
|
49
|
+
environments = [nil]
|
50
|
+
end
|
51
|
+
environments.each do |env|
|
52
|
+
check_platform_pinning(env)
|
53
|
+
end
|
54
|
+
# compile hiera files for all the nodes in every environment that is
|
55
|
+
# being deployed and only those environments.
|
56
|
+
compile_hiera_files(manager.filter(environments))
|
42
57
|
|
43
58
|
ssh_connect(nodes, connect_options(options)) do |ssh|
|
44
59
|
ssh.leap.log :checking, 'node' do
|
@@ -58,39 +73,130 @@ module LeapCli
|
|
58
73
|
end
|
59
74
|
end
|
60
75
|
end
|
76
|
+
if !Util.exit_status.nil? && Util.exit_status != 0
|
77
|
+
log :warning, "puppet did not finish successfully."
|
78
|
+
end
|
61
79
|
end
|
62
80
|
end
|
63
81
|
|
64
82
|
private
|
65
83
|
|
84
|
+
#
|
85
|
+
# The currently activated provider.json could have loaded some pinning
|
86
|
+
# information for the platform. If this is the case, refuse to deploy
|
87
|
+
# if there is a mismatch.
|
88
|
+
#
|
89
|
+
# For example:
|
90
|
+
#
|
91
|
+
# "platform": {
|
92
|
+
# "branch": "develop"
|
93
|
+
# "version": "1.0..99"
|
94
|
+
# "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD"
|
95
|
+
# }
|
96
|
+
#
|
97
|
+
def check_platform_pinning(environment)
|
98
|
+
provider = manager.env(environment).provider
|
99
|
+
return unless provider['platform']
|
100
|
+
|
101
|
+
if environment.nil? || environment == 'default'
|
102
|
+
provider_json = 'provider.json'
|
103
|
+
else
|
104
|
+
provider_json = 'provider.' + environment + '.json'
|
105
|
+
end
|
106
|
+
|
107
|
+
# can we have json schema verification already?
|
108
|
+
unless provider.platform.is_a? Hash
|
109
|
+
bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect)
|
110
|
+
end
|
111
|
+
|
112
|
+
# check version
|
113
|
+
if provider.platform['version']
|
114
|
+
if !Leap::Platform.version_in_range?(provider.platform.version)
|
115
|
+
say("The platform is pinned to a version range of '#{provider.platform.version}' "+
|
116
|
+
"by the `platform.version` property in #{provider_json}, but the platform "+
|
117
|
+
"(#{Path.platform}) has version #{Leap::Platform.version}.")
|
118
|
+
quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong version? ")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# check branch
|
123
|
+
if provider.platform['branch']
|
124
|
+
if !is_git_directory?(Path.platform)
|
125
|
+
say("The platform is pinned to a particular branch by the `platform.branch` property "+
|
126
|
+
"in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.")
|
127
|
+
quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ")
|
128
|
+
end
|
129
|
+
unless provider.platform.branch == current_git_branch(Path.platform)
|
130
|
+
say("The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+
|
131
|
+
"in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " +
|
132
|
+
"(for directory '#{Path.platform}')")
|
133
|
+
quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong branch? ")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# check commit
|
138
|
+
if provider.platform['commit']
|
139
|
+
if !is_git_directory?(Path.platform)
|
140
|
+
say("The platform is pinned to a particular commit range by the `platform.commit` property "+
|
141
|
+
"in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.")
|
142
|
+
quit!("OK. Bye.") unless agree("Do you really want to deploy anyway? ")
|
143
|
+
end
|
144
|
+
current_commit = current_git_commit(Path.platform)
|
145
|
+
Dir.chdir(Path.platform) do
|
146
|
+
commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'",
|
147
|
+
"The platform is pinned to a particular commit range by the `platform.commit` property "+
|
148
|
+
"in #{provider_json}, but git was not able to find commits in the range specified "+
|
149
|
+
"(#{provider.platform.commit}).")
|
150
|
+
commit_range = commit_range.split("\n")
|
151
|
+
if !commit_range.include?(current_commit) &&
|
152
|
+
provider.platform.commit.split('..').first != current_commit
|
153
|
+
say("The platform is pinned via the `platform.commit` property in #{provider_json} " +
|
154
|
+
"to a commit in the range #{provider.platform.commit}, but the current HEAD " +
|
155
|
+
"(#{current_commit}) is not in that range.")
|
156
|
+
quit!("OK. Bye.") unless agree("Do you really want to deploy from the wrong commit? ")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
66
162
|
def sync_hiera_config(ssh)
|
67
|
-
dest_dir = provider.hiera_sync_destination
|
68
163
|
ssh.rsync.update do |server|
|
69
164
|
node = manager.node(server.host)
|
70
165
|
hiera_file = Path.relative_path([:hiera, node.name])
|
71
|
-
ssh.leap.log hiera_file + ' -> ' + node.name + ':' +
|
166
|
+
ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
|
72
167
|
{
|
73
168
|
:source => hiera_file,
|
74
|
-
:dest =>
|
169
|
+
:dest => Leap::Platform.hiera_path,
|
75
170
|
:flags => "-rltp --chmod=u+rX,go-rwx"
|
76
171
|
}
|
77
172
|
end
|
78
173
|
end
|
79
174
|
|
175
|
+
#
|
176
|
+
# sync various support files.
|
177
|
+
#
|
80
178
|
def sync_support_files(ssh)
|
81
|
-
dest_dir =
|
179
|
+
dest_dir = Leap::Platform.files_dir
|
180
|
+
source_files = []
|
181
|
+
if Path.defined?(:custom_puppet_dir) && file_exists?(:custom_puppet_dir)
|
182
|
+
source_files += [:custom_puppet_dir, :custom_puppet_modules_dir, :custom_puppet_manifests_dir].collect{|path|
|
183
|
+
Path.relative_path(path, Path.provider) + '/' # rsync needs trailing slash
|
184
|
+
}
|
185
|
+
ensure_dir :custom_puppet_modules_dir
|
186
|
+
end
|
82
187
|
ssh.rsync.update do |server|
|
83
188
|
node = manager.node(server.host)
|
84
189
|
files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
|
190
|
+
files_to_sync += source_files
|
85
191
|
if files_to_sync.any?
|
86
192
|
ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
|
87
193
|
{
|
88
|
-
:chdir => Path.
|
194
|
+
:chdir => Path.named_path(:files_dir),
|
89
195
|
:source => ".",
|
90
196
|
:dest => dest_dir,
|
91
197
|
:excludes => "*",
|
92
|
-
:includes => calculate_includes_from_files(files_to_sync),
|
93
|
-
:flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --
|
198
|
+
:includes => calculate_includes_from_files(files_to_sync, '/files'),
|
199
|
+
:flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
|
94
200
|
}
|
95
201
|
else
|
96
202
|
nil
|
@@ -100,9 +206,9 @@ module LeapCli
|
|
100
206
|
|
101
207
|
def sync_puppet_files(ssh)
|
102
208
|
ssh.rsync.update do |server|
|
103
|
-
ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' +
|
209
|
+
ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
|
104
210
|
{
|
105
|
-
:dest =>
|
211
|
+
:dest => Leap::Platform.leap_dir,
|
106
212
|
:source => '.',
|
107
213
|
:chdir => Path.platform,
|
108
214
|
:excludes => '*',
|
@@ -112,7 +218,12 @@ module LeapCli
|
|
112
218
|
end
|
113
219
|
end
|
114
220
|
|
221
|
+
#
|
222
|
+
# ensure submodules are up to date, if the platform is a git
|
223
|
+
# repository.
|
224
|
+
#
|
115
225
|
def init_submodules
|
226
|
+
return unless is_git_directory?(Path.platform)
|
116
227
|
Dir.chdir Path.platform do
|
117
228
|
assert_run! "git submodule sync"
|
118
229
|
statuses = assert_run! "git submodule status"
|
@@ -126,11 +237,17 @@ module LeapCli
|
|
126
237
|
end
|
127
238
|
end
|
128
239
|
|
129
|
-
|
240
|
+
#
|
241
|
+
# converts an array of file paths into an array
|
242
|
+
# suitable for --include of rsync
|
243
|
+
#
|
244
|
+
# if set, `prefix` is stripped off.
|
245
|
+
#
|
246
|
+
def calculate_includes_from_files(files, prefix=nil)
|
130
247
|
return nil unless files and files.any?
|
131
248
|
|
132
249
|
# prepend '/' (kind of like ^ for rsync)
|
133
|
-
includes = files.collect {|file| '/' + file}
|
250
|
+
includes = files.collect {|file| file =~ /^\// ? file : '/' + file }
|
134
251
|
|
135
252
|
# include all sub files of specified directories
|
136
253
|
includes.size.times do |i|
|
@@ -148,6 +265,10 @@ module LeapCli
|
|
148
265
|
end
|
149
266
|
end
|
150
267
|
|
268
|
+
if prefix
|
269
|
+
includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')}
|
270
|
+
end
|
271
|
+
|
151
272
|
return includes
|
152
273
|
end
|
153
274
|
|
@@ -155,23 +276,11 @@ module LeapCli
|
|
155
276
|
if options[:tags]
|
156
277
|
tags = options[:tags].split(',')
|
157
278
|
else
|
158
|
-
tags =
|
279
|
+
tags = Leap::Platform.default_puppet_tags.dup
|
159
280
|
end
|
160
281
|
tags << 'leap_slow' unless options[:fast]
|
161
282
|
tags.join(',')
|
162
283
|
end
|
163
284
|
|
164
|
-
#
|
165
|
-
# for safety, we allow production deploys to be turned off in the Leapfile.
|
166
|
-
#
|
167
|
-
def filter_deploy_nodes(filter)
|
168
|
-
nodes = manager.filter!(filter)
|
169
|
-
if !leapfile.allow_production_deploy
|
170
|
-
nodes = nodes[:environment => "!production"]
|
171
|
-
assert! nodes.any?, "Skipping deploy because @allow_production_deploy is disabled."
|
172
|
-
end
|
173
|
-
nodes
|
174
|
-
end
|
175
|
-
|
176
285
|
end
|
177
286
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module LeapCli
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
desc "Manipulate and query environment information."
|
5
|
+
long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+
|
6
|
+
"A node in one environment will never interact with a node from another environment. "+
|
7
|
+
"Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+
|
8
|
+
"absolute file path of your provider directory (pins don't apply if you move the directory)"
|
9
|
+
command [:env, :e] do |c|
|
10
|
+
c.desc "List the available environments. The pinned environment, if any, will be marked with '*'. Will also set the pin if run with an environment argument."
|
11
|
+
c.arg_name 'ENVIRONMENT', :optional => true
|
12
|
+
c.command :ls do |ls|
|
13
|
+
ls.action do |global_options, options, args|
|
14
|
+
environment = get_env_from_args(args)
|
15
|
+
if environment
|
16
|
+
pin(environment)
|
17
|
+
LeapCli.leapfile.load
|
18
|
+
end
|
19
|
+
print_envs
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.'
|
24
|
+
c.arg_name 'ENVIRONMENT'
|
25
|
+
c.command :pin do |pin|
|
26
|
+
pin.action do |global_options,options,args|
|
27
|
+
environment = get_env_from_args(args)
|
28
|
+
if environment
|
29
|
+
pin(environment)
|
30
|
+
else
|
31
|
+
bail! "There is no environment `#{environment}`"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
c.desc "Unpin the environment. All subsequent commands will apply to all nodes."
|
37
|
+
c.command :unpin do |unpin|
|
38
|
+
unpin.action do |global_options, options, args|
|
39
|
+
LeapCli.leapfile.unset('environment')
|
40
|
+
log 0, :saved, "~/.leaprc, removing environment property."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
c.default_command :ls
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def get_env_from_args(args)
|
50
|
+
environment = args.first
|
51
|
+
if environment == 'default' || (environment && manager.environment_names.include?(environment))
|
52
|
+
return environment
|
53
|
+
else
|
54
|
+
return nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def pin(environment)
|
59
|
+
LeapCli.leapfile.set('environment', environment)
|
60
|
+
log 0, :saved, "~/.leaprc with environment set to #{environment}."
|
61
|
+
end
|
62
|
+
|
63
|
+
def print_envs
|
64
|
+
envs = ["default"] + manager.environment_names.compact.sort
|
65
|
+
envs.each do |env|
|
66
|
+
if env
|
67
|
+
if LeapCli.leapfile.environment == env
|
68
|
+
puts "* #{env}"
|
69
|
+
else
|
70
|
+
puts " #{env}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -79,14 +79,21 @@ module LeapCli; module Commands
|
|
79
79
|
private
|
80
80
|
|
81
81
|
def update_facts(global_options, options, args)
|
82
|
-
nodes = manager.filter(args)
|
82
|
+
nodes = manager.filter(args, :local => false)
|
83
83
|
new_facts = {}
|
84
84
|
ssh_connect(nodes) do |ssh|
|
85
85
|
ssh.leap.run_with_progress(facter_cmd) do |response|
|
86
|
-
|
86
|
+
node = manager.node(response[:host])
|
87
|
+
if node
|
88
|
+
new_facts[node.name] = response[:data].strip
|
89
|
+
else
|
90
|
+
log :warning, 'Could not find node for hostname %s' % response[:host]
|
91
|
+
end
|
87
92
|
end
|
88
93
|
end
|
89
|
-
|
94
|
+
# only overwrite the entire facts file if and only if we are gathering facts
|
95
|
+
# for all nodes in all environments.
|
96
|
+
overwrite_existing = args.empty? && LeapCli.leapfile.environment.nil?
|
90
97
|
update_facts_file(new_facts, overwrite_existing)
|
91
98
|
end
|
92
99
|
|
@@ -2,7 +2,7 @@ module LeapCli; module Commands
|
|
2
2
|
|
3
3
|
desc 'Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.'
|
4
4
|
arg_name 'FILE'
|
5
|
-
command :inspect do |c|
|
5
|
+
command [:inspect, :i] do |c|
|
6
6
|
c.switch 'base', :desc => 'Inspect the FILE from the provider_base (i.e. without local inheritance).', :negatable => false
|
7
7
|
c.action do |global_options,options,args|
|
8
8
|
object = args.first
|
@@ -109,7 +109,7 @@ module LeapCli; module Commands
|
|
109
109
|
if options[:base]
|
110
110
|
inspect_json manager.base_provider
|
111
111
|
elsif arg =~ /provider\.(.*)\.json/
|
112
|
-
inspect_json manager.
|
112
|
+
inspect_json manager.env($1).provider
|
113
113
|
else
|
114
114
|
inspect_json manager.provider
|
115
115
|
end
|
@@ -11,28 +11,29 @@ module LeapCli; module Commands
|
|
11
11
|
"`leap list openvpn +local` matches all nodes with service \"openvpn\" AND tag \"local\""
|
12
12
|
|
13
13
|
arg_name 'FILTER', :optional => true
|
14
|
-
command :list do |c|
|
14
|
+
command [:list,:ls] do |c|
|
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
|
+
# don't rely on default manager(), because we want to pass custom options to load()
|
19
|
+
manager = LeapCli::Config::Manager.new
|
18
20
|
if global_options[:color]
|
19
21
|
colors = ['cyan', 'white']
|
20
22
|
else
|
21
23
|
colors = [nil, nil]
|
22
24
|
end
|
23
25
|
puts
|
24
|
-
|
25
|
-
manager.load(:include_disabled => true) # reload, with disabled nodes
|
26
|
-
end
|
26
|
+
manager.load(:include_disabled => options['disabled'], :continue_on_error => true)
|
27
27
|
if options['print']
|
28
28
|
print_node_properties(manager.filter(args), options['print'])
|
29
29
|
else
|
30
30
|
if args.any?
|
31
31
|
NodeTable.new(manager.filter(args), colors).run
|
32
32
|
else
|
33
|
-
|
34
|
-
TagTable.new('
|
35
|
-
|
33
|
+
environment = LeapCli.leapfile.environment || '_all_'
|
34
|
+
TagTable.new('SERVICES', manager.env(environment).services, colors).run
|
35
|
+
TagTable.new('TAGS', manager.env(environment).tags, colors).run
|
36
|
+
NodeTable.new(manager.filter(), colors).run
|
36
37
|
end
|
37
38
|
end
|
38
39
|
end
|
@@ -41,11 +42,9 @@ module LeapCli; module Commands
|
|
41
42
|
private
|
42
43
|
|
43
44
|
def self.print_node_properties(nodes, properties)
|
44
|
-
node_list = manager.nodes
|
45
45
|
properties = properties.split(',')
|
46
46
|
max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max}
|
47
47
|
nodes.each_node do |node|
|
48
|
-
node.evaluate
|
49
48
|
value = properties.collect{|prop|
|
50
49
|
if node[prop].nil?
|
51
50
|
"null"
|
@@ -68,7 +67,7 @@ module LeapCli; module Commands
|
|
68
67
|
@colors = colors
|
69
68
|
end
|
70
69
|
def run
|
71
|
-
tags = @tag_list.keys.sort
|
70
|
+
tags = @tag_list.keys.select{|tag| tag !~ /^_/}.sort # sorted list of tags, excluding _partials
|
72
71
|
max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
|
73
72
|
table :border => false do
|
74
73
|
row :color => @colors[0] do
|
@@ -76,6 +75,7 @@ module LeapCli; module Commands
|
|
76
75
|
column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
|
77
76
|
end
|
78
77
|
tags.each do |tag|
|
78
|
+
next if @tag_list[tag].node_list.empty?
|
79
79
|
row :color => @colors[1] do
|
80
80
|
column tag
|
81
81
|
column @tag_list[tag].node_list.keys.sort.join(', ')
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
#
|
2
|
+
# fyi: the `node init` command lives in node_init.rb,
|
3
|
+
# but all other `node x` commands live here.
|
4
|
+
#
|
5
|
+
|
6
|
+
autoload :IPAddr, 'ipaddr'
|
4
7
|
|
5
8
|
module LeapCli; module Commands
|
6
9
|
|
@@ -9,7 +12,7 @@ module LeapCli; module Commands
|
|
9
12
|
##
|
10
13
|
|
11
14
|
desc 'Node management'
|
12
|
-
command :node do |node|
|
15
|
+
command [:node, :n] do |node|
|
13
16
|
node.desc 'Create a new configuration file for a node named NAME.'
|
14
17
|
node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
|
15
18
|
"The format is property_name:value.",
|
@@ -44,45 +47,6 @@ module LeapCli; module Commands
|
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
|
-
node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
|
48
|
-
node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
|
49
|
-
"copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
|
50
|
-
"Node init must be run before deploying to a server, and the server must be running and available via the network. " +
|
51
|
-
"This command only needs to be run once, but there is no harm in running it multiple times."
|
52
|
-
node.arg_name 'FILTER' #, :optional => false, :multiple => false
|
53
|
-
node.command :init do |init|
|
54
|
-
init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
|
55
|
-
init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
|
56
|
-
init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
|
57
|
-
|
58
|
-
init.action do |global,options,args|
|
59
|
-
assert! args.any?, 'You must specify a FILTER'
|
60
|
-
finished = []
|
61
|
-
manager.filter!(args).each_node do |node|
|
62
|
-
is_node_alive(node, options)
|
63
|
-
save_public_host_key(node, global, options) unless node.vagrant?
|
64
|
-
update_compiled_ssh_configs
|
65
|
-
ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
|
66
|
-
ssh_connect(node, ssh_connect_options) do |ssh|
|
67
|
-
if node.vagrant?
|
68
|
-
ssh.install_insecure_vagrant_key
|
69
|
-
end
|
70
|
-
ssh.install_authorized_keys
|
71
|
-
ssh.install_prerequisites
|
72
|
-
ssh.leap.capture(facter_cmd) do |response|
|
73
|
-
if response[:exitcode] == 0
|
74
|
-
update_node_facts(node.name, response[:data])
|
75
|
-
else
|
76
|
-
log :failed, "to run facter on #{node.name}"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
finished << node.name
|
81
|
-
end
|
82
|
-
log :completed, "initialization of nodes #{finished.join(', ')}"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
50
|
node.desc 'Renames a node file, and all its related files.'
|
87
51
|
node.arg_name 'OLD_NAME NEW_NAME'
|
88
52
|
node.command :mv do |mv|
|
@@ -117,30 +81,6 @@ module LeapCli; module Commands
|
|
117
81
|
## PUBLIC HELPERS
|
118
82
|
##
|
119
83
|
|
120
|
-
#
|
121
|
-
# generates the known_hosts file.
|
122
|
-
#
|
123
|
-
# we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
|
124
|
-
# for the possibility that the hostnames or ip has changed in the node configuration.
|
125
|
-
#
|
126
|
-
def update_known_hosts
|
127
|
-
buffer = StringIO.new
|
128
|
-
buffer << "#\n"
|
129
|
-
buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n"
|
130
|
-
buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n"
|
131
|
-
buffer << "#\n"
|
132
|
-
manager.nodes.keys.sort.each do |node_name|
|
133
|
-
node = manager.nodes[node_name]
|
134
|
-
hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
|
135
|
-
pub_key = read_file([:node_ssh_pub_key,node.name])
|
136
|
-
if pub_key
|
137
|
-
buffer << [hostnames, pub_key].join(' ')
|
138
|
-
buffer << "\n"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
write_file!(:known_hosts, buffer.string)
|
142
|
-
end
|
143
|
-
|
144
84
|
def get_node_from_args(args, options={})
|
145
85
|
node_name = args.first
|
146
86
|
node = manager.node(node_name)
|
@@ -151,71 +91,6 @@ module LeapCli; module Commands
|
|
151
91
|
node
|
152
92
|
end
|
153
93
|
|
154
|
-
private
|
155
|
-
|
156
|
-
##
|
157
|
-
## PRIVATE HELPERS
|
158
|
-
##
|
159
|
-
|
160
|
-
#
|
161
|
-
# saves the public ssh host key for node into the provider directory.
|
162
|
-
#
|
163
|
-
# see `man sshd` for the format of known_hosts
|
164
|
-
#
|
165
|
-
def save_public_host_key(node, global, options)
|
166
|
-
log :fetching, "public SSH host key for #{node.name}"
|
167
|
-
address = options[:ip] || node.ip_address
|
168
|
-
port = options[:port] || node.ssh.port
|
169
|
-
public_key = get_public_key_for_ip(address, port)
|
170
|
-
pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
|
171
|
-
if Path.exists?(pub_key_path)
|
172
|
-
if public_key == SshKey.load(pub_key_path)
|
173
|
-
log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
|
174
|
-
else
|
175
|
-
bail! do
|
176
|
-
log :error, "The public SSH host key we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
|
177
|
-
log "Remove the file #{pub_key_path} if you really want to change it.", :indent => 2
|
178
|
-
end
|
179
|
-
end
|
180
|
-
elsif public_key.in_known_hosts?(node.name, node.ip_address, node.domain.name)
|
181
|
-
log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
|
182
|
-
else
|
183
|
-
puts
|
184
|
-
say("This is the SSH host key you got back from node \"#{node.name}\"")
|
185
|
-
say("Type -- #{public_key.bits} bit #{public_key.type.upcase}")
|
186
|
-
say("Fingerprint -- " + public_key.fingerprint)
|
187
|
-
say("Public Key -- " + public_key.key)
|
188
|
-
if !global[:yes] && !agree("Is this correct? ")
|
189
|
-
bail!
|
190
|
-
else
|
191
|
-
puts
|
192
|
-
write_file! [:node_ssh_pub_key, node.name], public_key.to_s
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def get_public_key_for_ip(address, port=22)
|
198
|
-
assert_bin!('ssh-keyscan')
|
199
|
-
output = assert_run! "ssh-keyscan -p #{port} -t ecdsa #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
|
200
|
-
line = output.split("\n").grep(/^[^#]/).first
|
201
|
-
if line =~ /No route to host/
|
202
|
-
bail! :failed, 'ssh-keyscan: no route to %s' % address
|
203
|
-
elsif line =~ /no hostkey alg/
|
204
|
-
bail! :failed, 'ssh-keyscan: no hostkey alg (must be missing an ecdsa public host key)'
|
205
|
-
end
|
206
|
-
assert! line, "Got zero host keys back!"
|
207
|
-
ip, key_type, public_key = line.split(' ')
|
208
|
-
return SshKey.load(public_key, key_type)
|
209
|
-
end
|
210
|
-
|
211
|
-
def is_node_alive(node, options)
|
212
|
-
address = options[:ip] || node.ip_address
|
213
|
-
port = options[:port] || node.ssh.port
|
214
|
-
log :connecting, "to node #{node.name}"
|
215
|
-
assert_run! "nc -zw3 #{address} #{port}",
|
216
|
-
"Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
|
217
|
-
end
|
218
|
-
|
219
94
|
def seed_node_data(node, args)
|
220
95
|
args.each do |seed|
|
221
96
|
key, value = seed.split(':')
|