leap_cli 1.5.6 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/leap +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(':')
|