gaptool-client 0.8.0.pre.alpha3 → 0.8.0.pre.alpha4
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.
- 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
|