gaptool-client 0.8.0.pre.alpha3 → 0.8.0.pre.alpha4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/gt +1 -0
- data/lib/gaptool_client.rb +2 -597
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3528f94e83fb0e8209a053b24683729e830b61ef
|
4
|
+
data.tar.gz: 1f768dc1e8b37c94b289d00cce7e5935506adf13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55aaebc78a856cfd8b3d92789507855c3b394a04c3d66ce2d6c3ad1283a80a5b5ab73773747a502b01f28f661a1574d36d199affb7f59855301c3f964a9d5310
|
7
|
+
data.tar.gz: 63fbd7d33485fba03a7de11c19db639bf096cd6286ffa31085481fedeed1c62016ae616ad5da7ed2f2363455eaef24618163bc1ff215b51a672eae5a7f506886
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.8.0-
|
1
|
+
0.8.0-alpha4
|
data/bin/gt
CHANGED
data/lib/gaptool_client.rb
CHANGED
@@ -1,604 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# coding: utf-8
|
3
|
-
# rubocop:disable Metrics/LineLength
|
3
|
+
# rubocop:disable Metrics/LineLength
|
4
4
|
|
5
|
-
require 'rainbow'
|
6
|
-
require 'json'
|
7
5
|
require 'clamp'
|
8
|
-
require '
|
9
|
-
require 'sshkit/dsl'
|
10
|
-
require 'set'
|
11
|
-
require 'gaptool-api'
|
12
|
-
require 'logger'
|
6
|
+
require 'gaptool_client/commands'
|
13
7
|
|
14
8
|
module Gaptool
|
15
|
-
START_MARKER = '###### GAPTOOL ######'
|
16
|
-
STOP_MARKER = '###### END GAPTOOL ######'
|
17
|
-
|
18
|
-
def self.api
|
19
|
-
@api ||= GTAPI::GaptoolServer.new(
|
20
|
-
ENV['GT_USER'], ENV['GT_KEY'],
|
21
|
-
ENV['GT_URL'], ENV['GT_AWS_ZONE']
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.infohelper(nodes, parseable, grepable, short = false)
|
26
|
-
if parseable && !short
|
27
|
-
puts({ nodes: nodes }.to_json)
|
28
|
-
elsif parseable
|
29
|
-
puts({ nodes: nodes.map { |node| node.select { |k, _v| %w(role environment instance).include?(k) } } }.to_json)
|
30
|
-
else
|
31
|
-
nodes.each do |node|
|
32
|
-
host = "#{node['role']}:#{node['environment']}:#{node['instance']}"
|
33
|
-
if grepable && short
|
34
|
-
puts host
|
35
|
-
elsif !grepable
|
36
|
-
puts Rainbow(host).green
|
37
|
-
end
|
38
|
-
next if short
|
39
|
-
keys = node.keys.sort
|
40
|
-
keys.each do |key|
|
41
|
-
value = node[key]
|
42
|
-
if grepable
|
43
|
-
puts "#{host}|#{key}|#{value}"
|
44
|
-
else
|
45
|
-
value = Time.at(node[key].to_i) if key == 'launch_time'
|
46
|
-
if key == keys.last
|
47
|
-
puts " ┖ #{Rainbow(key).cyan}: #{value}\n\n"
|
48
|
-
else
|
49
|
-
puts " ┠ #{Rainbow(key).cyan}: #{value}"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.error(message, opts = {})
|
58
|
-
code = opts[:code] || 1
|
59
|
-
color = opts[:color] || :red
|
60
|
-
STDERR.puts(Rainbow(message).send(color))
|
61
|
-
exit code
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.get_host(node)
|
65
|
-
"#{node['role']}-#{node['environment']}-#{node['instance']}"
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.ssh_snip_for_node(node)
|
69
|
-
host = Gaptool.get_host(node)
|
70
|
-
<<-EOF
|
71
|
-
# -- #{node['instance']}
|
72
|
-
Host #{host} #{node['instance']}
|
73
|
-
Hostname #{node['hostname']}
|
74
|
-
User #{ENV['GT_USER']}
|
75
|
-
LogLevel FATAL
|
76
|
-
PreferredAuthentications publickey
|
77
|
-
CheckHostIP no
|
78
|
-
StrictHostKeyChecking no
|
79
|
-
UserKnownHostsFile /dev/null
|
80
|
-
# -- end #{node['instance']}
|
81
|
-
EOF
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.ssh_config
|
85
|
-
config = File.join(Dir.home, '.ssh', 'config')
|
86
|
-
dir = File.dirname(config)
|
87
|
-
parent = File.join(dir, '..')
|
88
|
-
if File.exist?(config)
|
89
|
-
Gaptool.error "#{config}: not writable" unless File.writable?(config)
|
90
|
-
content = File.read(config)
|
91
|
-
# puts Rainbow("Creating backup file at #{config}.bck").green
|
92
|
-
File.open("#{config}.bck", 'w') { |f| f.write(content) }
|
93
|
-
else
|
94
|
-
if !File.exist?(dir)
|
95
|
-
Gaptool.error "Home directory #{parent} does not exists"\
|
96
|
-
unless Dir.exist?(parent)
|
97
|
-
Dir.mkdir(dir, 0700)
|
98
|
-
elsif !File.directory?(dir)
|
99
|
-
Gaptool.error "#{dir}: not a directory"
|
100
|
-
end
|
101
|
-
content = ''
|
102
|
-
end
|
103
|
-
[config, content]
|
104
|
-
end
|
105
|
-
|
106
|
-
def self.update_ssh_config_for(nodes, verbose=true)
|
107
|
-
nodes = [nodes] unless nodes.is_a?(Array)
|
108
|
-
config, content = Gaptool.ssh_config
|
109
|
-
nodes.each do |node|
|
110
|
-
snip = Gaptool.ssh_snip_for_node(node)
|
111
|
-
mark = "# -- #{node['instance']}"
|
112
|
-
emark = "# -- end #{node['instance']}"
|
113
|
-
|
114
|
-
if Regexp.new(mark).match(content)
|
115
|
-
puts Rainbow("Updating ssh config for #{get_host(node)}").green if verbose
|
116
|
-
content.gsub!(/#{mark}.*?#{emark}/m, snip.strip)
|
117
|
-
elsif Regexp.new(Gaptool::STOP_MARKER).match(content)
|
118
|
-
puts Rainbow("Adding ssh config for #{get_host(node)}").green if verbose
|
119
|
-
content.gsub!(/#{Gaptool::STOP_MARKER}/m, snip + "\n" + Gaptool::STOP_MARKER)
|
120
|
-
else
|
121
|
-
puts Rainbow('No gt ssh config found: please run gt ssh-config').yellow
|
122
|
-
puts Rainbow("Adding ssh config for #{get_host(node)}").green if verbose
|
123
|
-
content = <<-EOF
|
124
|
-
#{content}
|
125
|
-
#{Gaptool::START_MARKER}
|
126
|
-
#{snip}
|
127
|
-
#{Gaptool::STOP_MARKER}
|
128
|
-
EOF
|
129
|
-
end
|
130
|
-
end
|
131
|
-
File.open(config, 'w') { |f| f.write(content) }
|
132
|
-
end
|
133
|
-
|
134
|
-
def self.configure_sshkit
|
135
|
-
SSHKit.config.output_verbosity = Logger::WARN
|
136
|
-
SSHKit::Backend::Netssh.configure do |ssh|
|
137
|
-
ssh.connection_timeout = 30
|
138
|
-
ssh.ssh_options = {
|
139
|
-
forward_agent: true,
|
140
|
-
global_known_hosts_file: '/dev/null',
|
141
|
-
keys_only: true,
|
142
|
-
port: 22,
|
143
|
-
user: ENV['GT_USER']
|
144
|
-
}
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def self.query_nodes(opts)
|
149
|
-
instance = opts[:instance]
|
150
|
-
role = opts[:role]
|
151
|
-
environment = opts[:environment]
|
152
|
-
params = opts[:params]
|
153
|
-
|
154
|
-
if instance
|
155
|
-
puts Rainbow('Ignoring role and environment as instance is set').red if role || environment
|
156
|
-
[Gaptool.api.getonenode(instance)]
|
157
|
-
elsif role && environment
|
158
|
-
Gaptool.api.getenvroles(role, environment, params)
|
159
|
-
elsif role
|
160
|
-
Gaptool.api.getrolenodes(role, params)
|
161
|
-
elsif environment
|
162
|
-
Gaptool.api.getenvnodes(environment, params)
|
163
|
-
else
|
164
|
-
Gaptool.api.getallnodes(params)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def self.remote_exec(nodes, commands, opts = {})
|
169
|
-
serial = opts[:serial]
|
170
|
-
group_size = opts[:group_size] || 10
|
171
|
-
if opts[:update_ssh_config].nil? || opts[:update_ssh_config]
|
172
|
-
nodes.each { |n| Gaptool.update_ssh_config_for(n, false) }
|
173
|
-
end
|
174
|
-
pre = opts[:pre_hooks] || []
|
175
|
-
post = opts[:post_hooks] || []
|
176
|
-
nodes = Hash[nodes.map { |n| [n['hostname'], n] }]
|
177
|
-
handlers = Hash[nodes.map { |hostname, node| [hostname, InteractionHandler.new(Gaptool.get_host(node))] }]
|
178
|
-
opts = { in: :groups, limit: group_size }
|
179
|
-
opts = { in: :sequence } if serial
|
180
|
-
Gaptool.configure_sshkit
|
181
|
-
on(nodes.keys, opts) do |host|
|
182
|
-
pre.each { |h| instance_exec(nodes[host.hostname], &h) }
|
183
|
-
commands.each do |cmd|
|
184
|
-
execute(:bash, "-l -c '#{cmd}'", interaction_handler: handlers[host.hostname])
|
185
|
-
end
|
186
|
-
post.each { |h| instance_exec(nodes[host.hostname], &h) }
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.split_attrs(attribute_list)
|
191
|
-
opts = {}
|
192
|
-
attribute_list.each do |attr_|
|
193
|
-
key, value = attr_.split('=', 2)
|
194
|
-
split = key.split('.')
|
195
|
-
cur = opts
|
196
|
-
split.each_with_index do |part, idx|
|
197
|
-
if idx == split.size - 1
|
198
|
-
# leaf, add the value
|
199
|
-
cur[part] = value
|
200
|
-
else
|
201
|
-
cur[part] ||= {}
|
202
|
-
end
|
203
|
-
cur = cur[part]
|
204
|
-
end
|
205
|
-
end
|
206
|
-
opts
|
207
|
-
end
|
208
|
-
|
209
|
-
class InteractionHandler
|
210
|
-
attr_reader :host
|
211
|
-
def initialize(host)
|
212
|
-
@host = host
|
213
|
-
end
|
214
|
-
|
215
|
-
def on_data(_command, stream_name, data, _channel)
|
216
|
-
case stream_name
|
217
|
-
when :stdout
|
218
|
-
puts "#{Rainbow("#{@host}").yellow}> #{data}"
|
219
|
-
when :stderr
|
220
|
-
STDERR.puts "#{Rainbow("#{@host}").red}> #{data}"
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
class InitCommand < Clamp::Command
|
226
|
-
option ['-r', '--role'], 'ROLE', 'Resource name to initilize', required: true
|
227
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Which environment, e.g. production', required: true
|
228
|
-
option ['-z', '--zone'], 'ZONE', 'AWS availability zone to put node in', default: 'us-west-2c'
|
229
|
-
option ['-t', '--type'], 'TYPE', 'Type of instance, e.g. m1.large', required: true
|
230
|
-
option ['-s', '--security-group'], 'SECURITY_GROUP', 'Security group name. Defaults to $role-$environment'
|
231
|
-
option ['-a', '--ami'], 'AMI_ID', 'Use a specific AMI for the instance (i.e. ami-xxxxxxx)'
|
232
|
-
option ['-C', '--chef-repo'], 'GITURL', 'git url for the chef repository'
|
233
|
-
option ['-b', '--chef-branch'], 'BRANCH', 'branch of the chef repository to use'
|
234
|
-
option(['-R', '--chef_runlist'], 'RECIPE|ROLE',
|
235
|
-
'override chef run_list. recipe[cb::recipe] or role[myrole]. Can be specified multiple times',
|
236
|
-
multivalued: true, attribute_name: 'chef_runlist')
|
237
|
-
option ['--no-terminate'], :flag, 'Add terminate protection'
|
238
|
-
def execute
|
239
|
-
no_terminate = no_terminate? ? true : nil
|
240
|
-
Gaptool.api.addnode(zone, type, role, environment, nil, security_group,
|
241
|
-
ami, chef_repo, chef_branch, chef_runlist,
|
242
|
-
no_terminate)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
class TerminateCommand < Clamp::Command
|
247
|
-
option ['-i', '--instance'], 'INSTANCE', 'Instance ID, e.g. i-12345678', required: true
|
248
|
-
option ['-z', '--zone'], 'ZONE', 'AWS region of the node (deprecated/ignored)'
|
249
|
-
option ['-r', '--role'], 'ROLE', 'Resource name to initilize', required: true
|
250
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Which environment, e.g. production', required: true
|
251
|
-
|
252
|
-
def execute
|
253
|
-
node = Gaptool.api.getonenode(instance)
|
254
|
-
nodes = [node]
|
255
|
-
Gaptool.infohelper(nodes, false, false)
|
256
|
-
zone = node['zone'][0..-2]
|
257
|
-
if node['environment'] != environment || node['role'] != role
|
258
|
-
puts Rainbow("'#{node['role']}-#{node['environment']}' do not match provided value (#{role}-#{environment})").red
|
259
|
-
exit 1
|
260
|
-
end
|
261
|
-
if node['terminate'] == 'false'
|
262
|
-
puts Rainbow('"terminate" command is disabled for this instance').red
|
263
|
-
exit 2
|
264
|
-
end
|
265
|
-
print Rainbow('Terminate instance? [type yes to confirm]: ').green
|
266
|
-
res = $stdin.gets.chomp
|
267
|
-
return 0 unless res.downcase == 'yes'
|
268
|
-
puts "Terminating instance #{node['role']}:#{node['environment']}:#{node['instance']} in region #{zone}"
|
269
|
-
begin
|
270
|
-
Gaptool.api.terminatenode(instance, zone)
|
271
|
-
rescue
|
272
|
-
puts Rainbow('Cannot terminate instance').red
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
class RuncmdCommand < Clamp::Command
|
278
|
-
option ['-r', '--role'], 'ROLE', 'Instance role'
|
279
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Instance environment'
|
280
|
-
option ['-i', '--instance'], 'INSTANCE', 'Instance id (i-xxxxxxxx)'
|
281
|
-
option ['-s', '--serial'], :flag, 'Run command serially. Order of execution is unknown.'
|
282
|
-
option ['-x', '--exclude-hidden'], :flag, 'Exclude hidden hosts'
|
283
|
-
parameter 'COMMAND ...', 'Command to run', attribute_name: :commands
|
284
|
-
|
285
|
-
def execute
|
286
|
-
params = exclude_hidden? ? {} : { hidden: true }
|
287
|
-
nodes = Gaptool.query_nodes(params.merge(instance: instance,
|
288
|
-
role: role, environment: environment))
|
289
|
-
Gaptool.remote_exec(nodes, [commands.join(' ')], serial: serial?)
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
class SSHConfigCommand < Clamp::Command
|
294
|
-
option ['-r', '--remove'], :flag, 'Remove ssh configuration'
|
295
|
-
|
296
|
-
def execute
|
297
|
-
config, content = Gaptool.ssh_config
|
298
|
-
|
299
|
-
if remove?
|
300
|
-
data = ''
|
301
|
-
else
|
302
|
-
puts Rainbow('Getting nodes from API').green
|
303
|
-
data = [Gaptool::START_MARKER, '# to remove this block, run gt ssh-config --remove']
|
304
|
-
Gaptool.api.getallnodes(hidden: true).sort_by { |n| n['instance'] }.each do |node|
|
305
|
-
host = Gaptool.get_host(node)
|
306
|
-
puts " - #{Rainbow(host).blue}"
|
307
|
-
data << Gaptool.ssh_snip_for_node(node)
|
308
|
-
end
|
309
|
-
data << Gaptool::STOP_MARKER
|
310
|
-
data = data.join("\n")
|
311
|
-
end
|
312
|
-
|
313
|
-
if Regexp.new(Gaptool::START_MARKER).match(content)
|
314
|
-
content.gsub!(/#{Gaptool::START_MARKER}.*?#{Gaptool::STOP_MARKER}/m, data)
|
315
|
-
elsif !remove?
|
316
|
-
content = content + "\n" + data
|
317
|
-
end
|
318
|
-
File.open(config, 'w') { |f| f.write(content) }
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
class SSHCommand < Clamp::Command
|
323
|
-
option ['-r', '--role'], 'ROLE', 'Instance role'
|
324
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Instance environment'
|
325
|
-
option ['-i', '--instance'], 'INSTANCE', 'Instance id (i-xxxxxxxx)'
|
326
|
-
option ['-f', '--first'], :flag, 'Just connect to first available instance'
|
327
|
-
option ['-t', '--tmux'], :flag, 'No-op, DEPRECATED'
|
328
|
-
|
329
|
-
def execute
|
330
|
-
puts Rainbow('tmux support has been removed').yellow if tmux?
|
331
|
-
nodes = Gaptool.query_nodes(hidden: true,
|
332
|
-
instance: instance,
|
333
|
-
environment: environment,
|
334
|
-
role: role)
|
335
|
-
|
336
|
-
if first? || (nodes.length == 1 && !instance)
|
337
|
-
puts Rainbow('No instance specified, but only one instance in cluster or first forced').green
|
338
|
-
node = nodes.first
|
339
|
-
elsif !instance
|
340
|
-
nodes.each_index do |i|
|
341
|
-
puts "#{i}: #{nodes[i]['instance']}"
|
342
|
-
end
|
343
|
-
print Rainbow('Select a node: ').cyan
|
344
|
-
node = nodes[$stdin.gets.chomp.to_i]
|
345
|
-
error 'Invalid selection' if node.nil?
|
346
|
-
else
|
347
|
-
node = nodes.first
|
348
|
-
end
|
349
|
-
Gaptool.update_ssh_config_for(node)
|
350
|
-
system "ssh #{node['instance']}"
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
class InfoCommand < Clamp::Command
|
355
|
-
option ['-r', '--role'], 'ROLE', 'Role name, e.g. frontend'
|
356
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Which environment, e.g. production'
|
357
|
-
option ['-i', '--instance'], 'INSTANCE', 'Node instance, leave blank to query avilable nodes'
|
358
|
-
option ['-s', '--short'], :flag, 'Only show summary for hosts'
|
359
|
-
option ['-p', '--parseable'], :flag, 'Display in non-pretty parseable JSON'
|
360
|
-
option ['-g', '--grepable'], :flag, 'Display in non-pretty grep-friendly text'
|
361
|
-
option ['-H', '--hidden'], :flag, 'Display hidden hosts'
|
362
|
-
|
363
|
-
def execute
|
364
|
-
nodes = []
|
365
|
-
params = hidden? ? { hidden: true } : {}
|
366
|
-
nodes = Gaptool.query_nodes(params.merge(instance: instance,
|
367
|
-
role: role,
|
368
|
-
environment: environment))
|
369
|
-
Gaptool.infohelper(nodes, parseable?, grepable?, short?)
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
class SetCommand < Clamp::Command
|
374
|
-
option ['-i', '--instance'], 'INSTANCE', 'Node instance, required', required: true
|
375
|
-
option ['-p', '--parseable'], :flag, 'Display in non-pretty parseable JSON'
|
376
|
-
option ['-g', '--grepable'], :flag, 'Display in non-pretty grep-friendly text'
|
377
|
-
option ['-k', '--parameter'], 'NAME', 'Set parameter for the node', required: true, multivalued: true
|
378
|
-
option ['-v', '--value'], 'VALUE', 'Value for parameter', required: true, multivalued: true
|
379
|
-
|
380
|
-
def convert_bool(v)
|
381
|
-
case v.downcase
|
382
|
-
when 'true'
|
383
|
-
true
|
384
|
-
when 'false'
|
385
|
-
false
|
386
|
-
else
|
387
|
-
v
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
def execute
|
392
|
-
if parameter_list.length != value_list.length
|
393
|
-
puts Rainbow('parameter and value length mismatch').red
|
394
|
-
end
|
395
|
-
params = Hash[parameter_list.each_with_index.map { |p, i| [p, convert_bool(value_list[i])] }]
|
396
|
-
Gaptool.infohelper([Gaptool.api.setparameters(instance, params)], parseable?, grepable?)
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
class ChefrunCommand < Clamp::Command
|
401
|
-
option ['-r', '--role'], 'ROLE', 'Role name to ssh to'
|
402
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Which environment, e.g. production'
|
403
|
-
option ['-i', '--instance'], 'INSTANCE', 'Instance ID, e.g. i-12345678'
|
404
|
-
option ['-H', '--hidden'], :flag, 'Include hidden hosts'
|
405
|
-
option(['-A', '--attribute'], 'ATTRIBUTE',
|
406
|
-
'Pass one or more parameters to the deploy recipe in recipe.attr=value format',
|
407
|
-
multivalued: true)
|
408
|
-
option(['-b', '--chef-branch'], 'BRANCH',
|
409
|
-
'branch of the chef repository to use (defaults to last branch used during init/chefrun)',
|
410
|
-
default: nil)
|
411
|
-
option ['-s', '--serial'], :flag, 'Run command serially. Order of execution is unknown.'
|
412
|
-
option ['-W', '--whyrun'], :flag, 'Whyrun, like dry-run but different.'
|
413
|
-
|
414
|
-
def execute
|
415
|
-
attrs = Gaptool.split_attrs(attribute_list)
|
416
|
-
nodes = Gaptool.query_nodes(hidden: hidden? ? true : nil,
|
417
|
-
role: role,
|
418
|
-
instance: instance,
|
419
|
-
environment: environment)
|
420
|
-
|
421
|
-
nodes = nodes.map do |x|
|
422
|
-
x['whyrun'] = whyrun?
|
423
|
-
x['chef_branch'] = chef_branch
|
424
|
-
x['attrs'] = attrs
|
425
|
-
x
|
426
|
-
end
|
427
|
-
|
428
|
-
pre_hook = Proc.new do |node|
|
429
|
-
if node['chef_runlist'].nil?
|
430
|
-
runlist = ['recipe[main]']
|
431
|
-
elsif node['chef_runlist'].is_a? Array
|
432
|
-
runlist = node['chef_runlist']
|
433
|
-
else
|
434
|
-
runlist = eval(node['chef_runlist'])
|
435
|
-
end
|
436
|
-
json = {
|
437
|
-
'this_server' => "#{node['role']}-#{node['environment']}-#{node['instance']}",
|
438
|
-
'role' => node['role'],
|
439
|
-
'environment' => node['environment'],
|
440
|
-
'app_user' => node['appuser'],
|
441
|
-
'run_list' => runlist,
|
442
|
-
'hostname' => node['hostname'],
|
443
|
-
'instance' => node['instance'],
|
444
|
-
'zone' => node['zone'],
|
445
|
-
'itype' => node['itype'],
|
446
|
-
'apps' => eval(node['apps'] || '[]'),
|
447
|
-
'gaptool' => {
|
448
|
-
'user' => ENV['GT_USER'],
|
449
|
-
'key' => ENV['GT_KEY'],
|
450
|
-
'url' => ENV['GT_URL']
|
451
|
-
}
|
452
|
-
}.merge(node['attrs'])
|
453
|
-
git = 'sudo -u admin git'
|
454
|
-
pull = "#{git} fetch --all; #{git} reset --hard origin/`#{git} rev-parse --abbrev-ref HEAD`"
|
455
|
-
wopts = node['whyrun'] ? ' -W ' : ''
|
456
|
-
|
457
|
-
unless node['chef_branch'].nil?
|
458
|
-
json['chefbranch'] = node['chef_branch']
|
459
|
-
pull = "#{git} checkout -f #{node['chef_branch']}; #{git} fetch --all; #{git} reset --hard origin/#{node['chef_branch']}"
|
460
|
-
end
|
461
|
-
upload!(StringIO.new(json.to_json), '/tmp/chef.json')
|
462
|
-
script = <<-EOS
|
463
|
-
cd /var/data/admin/ops
|
464
|
-
#{pull}
|
465
|
-
sudo chef-solo -c /var/data/admin/ops/cookbooks/solo.rb -j /tmp/chef.json -E #{node['environment']}#{wopts}
|
466
|
-
rm -f /tmp/chef.json
|
467
|
-
EOS
|
468
|
-
upload!(StringIO.new(script), '/tmp/chef.sh')
|
469
|
-
end
|
470
|
-
Gaptool.remote_exec(nodes,
|
471
|
-
['chmod +x /tmp/chef.sh', '/tmp/chef.sh', 'rm -f /tmp/chef.sh'],
|
472
|
-
pre_hooks: [pre_hook], serial: serial?)
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
class DeployCommand < Clamp::Command
|
477
|
-
option(['-a', '--app'], 'APP',
|
478
|
-
'Application(s) to deploy (can be set multiple times)',
|
479
|
-
required: true, multivalued: true)
|
480
|
-
option ['-m', '--migrate'], :flag, 'Toggle running migrations'
|
481
|
-
option ['-e', '--environment'], 'ENVIRONMENT', 'Which environment, e.g. production', required: true
|
482
|
-
option ['-b', '--branch'], 'BRANCH', 'Git branch to deploy, default is master'
|
483
|
-
option ['-r', '--rollback'], :flag, 'Toggle this to rollback last deploy'
|
484
|
-
option ['-i', '--instance'], 'INSTANCE', 'Instance ID, e.g. i-12345678. If set, all applications MUST be hosted on this node.'
|
485
|
-
option ['-A', '--attribute'], 'ATTRIBUTE', 'Pass one or more parameters to the deploy recipe in recipe.attr=value format', multivalued: true
|
486
|
-
option ['-H', '--hidden'], :flag, 'Display hidden hosts'
|
487
|
-
option ['-s', '--serial'], :flag, 'Run command serially. Order of execution is unknown.'
|
488
|
-
|
489
|
-
def execute # rubocop:disable Metrics/MethodLength
|
490
|
-
attrs = Gaptool.split_attrs(attribute_list)
|
491
|
-
if instance
|
492
|
-
n = Gaptool.api.getonenode(instance)
|
493
|
-
if n['environment'] != environment
|
494
|
-
Gaptool.error "Instance #{instance} is not in environment #{environment}"
|
495
|
-
else
|
496
|
-
app_list.each do |app|
|
497
|
-
Gaptool.error "Instance #{instance} does not host #{app} in env #{environment}" \
|
498
|
-
unless n['apps'].include?(app)
|
499
|
-
end
|
500
|
-
end
|
501
|
-
nodes = [n]
|
502
|
-
|
503
|
-
else
|
504
|
-
params = hidden? ? { hidden: true } : {}
|
505
|
-
nodes = []
|
506
|
-
app_list.each do |app|
|
507
|
-
nodes.concat(Gaptool.api.getappnodes(app, environment, params))
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
# dedup nodes
|
512
|
-
seen = Set.new
|
513
|
-
app_set = Set.new(app_list)
|
514
|
-
nodes = nodes.select do |x|
|
515
|
-
res = !seen.include?(x['instance'])
|
516
|
-
seen << x['instance']
|
517
|
-
res
|
518
|
-
end
|
519
|
-
nodes = nodes.map do |x|
|
520
|
-
x['apps'] = eval(x['apps'])
|
521
|
-
x['apps_to_deploy'] = (Set.new(x['apps']) & app_set).to_a
|
522
|
-
x['rollback'] = rollback?
|
523
|
-
x['branch'] = branch || 'master'
|
524
|
-
x['migrate'] = migrate?
|
525
|
-
x['attrs'] = attrs
|
526
|
-
x
|
527
|
-
end
|
528
|
-
|
529
|
-
pre_hook = Proc.new do |node|
|
530
|
-
host = "#{node['role']}:#{node['environment']}:#{node['instance']}"
|
531
|
-
puts "#{Rainbow('Deploying apps').cyan} '" + \
|
532
|
-
Rainbow(node['apps_to_deploy'].join(' ')).green + \
|
533
|
-
"' #{Rainbow('on').cyan} " + \
|
534
|
-
Rainbow(host).green
|
535
|
-
|
536
|
-
if node['chef_runlist'].nil?
|
537
|
-
runlist = ['recipe[deploy]']
|
538
|
-
elsif node['chef_runlist'].is_a? Array
|
539
|
-
runlist = node['chef_runlist']
|
540
|
-
else
|
541
|
-
runlist = eval(node['chef_runlist'])
|
542
|
-
end
|
543
|
-
json = {
|
544
|
-
'this_server' => "#{node['role']}-#{node['environment']}-#{node['instance']}",
|
545
|
-
'role' => node['role'],
|
546
|
-
'environment' => node['environment'],
|
547
|
-
'app_user' => node['appuser'],
|
548
|
-
'run_list' => runlist,
|
549
|
-
'hostname' => node['hostname'],
|
550
|
-
'instance' => node['instance'],
|
551
|
-
'zone' => node['zone'],
|
552
|
-
'itype' => node['itype'],
|
553
|
-
'apps' => node['apps'],
|
554
|
-
'deploy_apps' => node['apps_to_deploy'],
|
555
|
-
'rollback' => node['rollback'],
|
556
|
-
'branch' => node['branch'],
|
557
|
-
'migrate' => node['migrate'],
|
558
|
-
'gaptool' => {
|
559
|
-
'user' => ENV['GT_USER'],
|
560
|
-
'key' => ENV['GT_KEY'],
|
561
|
-
'url' => ENV['GT_URL']
|
562
|
-
}
|
563
|
-
}.merge(node['attrs']).to_json
|
564
|
-
upload!(StringIO.new(json), '/tmp/chef.json')
|
565
|
-
script = <<-EOS
|
566
|
-
cd /var/data/admin/ops
|
567
|
-
sudo -u admin git pull
|
568
|
-
sudo chef-solo -c /var/data/admin/ops/cookbooks/solo.rb -j /tmp/chef.json -E #{node['environment']}
|
569
|
-
rm -f /tmp/chef.json
|
570
|
-
EOS
|
571
|
-
upload!(StringIO.new(script), '/tmp/deploy.sh')
|
572
|
-
end
|
573
|
-
|
574
|
-
Gaptool.remote_exec(nodes,
|
575
|
-
['chmod +x /tmp/deploy.sh', '/tmp/deploy.sh', 'rm -f /tmp/deploy.sh'],
|
576
|
-
pre_hooks: [pre_hook], serial: serial?)
|
577
|
-
end
|
578
|
-
end
|
579
|
-
|
580
|
-
class RehashCommand < Clamp::Command
|
581
|
-
option ['-y', '--yes'], :flag, 'YES I REALLY WANT TO DO THIS'
|
582
|
-
def execute
|
583
|
-
if yes?
|
584
|
-
puts Gaptool.api.rehash
|
585
|
-
else
|
586
|
-
puts "You need to run this with -y\nIf you don't know what this does or aren't sure, DO NOT RUN IT\nThis will regenerate all host metadata on gaptool-server\nand can break in-progress operations."
|
587
|
-
end
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
class VersionCommand < Clamp::Command
|
592
|
-
option ['-r', '--remote'], :flag, 'Include remote API version'
|
593
|
-
def execute
|
594
|
-
version = File.read(File.realpath(File.join(File.dirname(__FILE__), '..', 'VERSION'))).strip
|
595
|
-
puts "gaptool-client #{version} using gaptool-api #{Gaptool.api.version}"
|
596
|
-
return 0 unless remote?
|
597
|
-
vinfo = Gaptool.api.api_version
|
598
|
-
puts "gaptool-server #{vinfo['server_version']}"
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
9
|
class MainCommand < Clamp::Command
|
603
10
|
subcommand 'info', 'Displays information about nodes', InfoCommand
|
604
11
|
subcommand 'init', 'Create new application cluster', InitCommand
|
@@ -613,5 +20,3 @@ EOS
|
|
613
20
|
subcommand 'version', 'Show version', VersionCommand
|
614
21
|
end
|
615
22
|
end
|
616
|
-
|
617
|
-
Gaptool::MainCommand.run
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gaptool-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.0.pre.
|
4
|
+
version: 0.8.0.pre.alpha4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francesco Laurita
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-12-
|
13
|
+
date: 2015-12-31 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: json
|