turbot 0.0.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.
Files changed (71) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +36 -0
  3. data/bin/turbot +17 -0
  4. data/data/cacert.pem +3988 -0
  5. data/lib/turbot/auth.rb +315 -0
  6. data/lib/turbot/cli.rb +38 -0
  7. data/lib/turbot/client/cisaurus.rb +25 -0
  8. data/lib/turbot/client/pgbackups.rb +113 -0
  9. data/lib/turbot/client/rendezvous.rb +111 -0
  10. data/lib/turbot/client/ssl_endpoint.rb +25 -0
  11. data/lib/turbot/client/turbot_postgresql.rb +148 -0
  12. data/lib/turbot/client.rb +757 -0
  13. data/lib/turbot/command/auth.rb +85 -0
  14. data/lib/turbot/command/base.rb +192 -0
  15. data/lib/turbot/command/bots.rb +326 -0
  16. data/lib/turbot/command/config.rb +123 -0
  17. data/lib/turbot/command/help.rb +179 -0
  18. data/lib/turbot/command/keys.rb +115 -0
  19. data/lib/turbot/command/logs.rb +34 -0
  20. data/lib/turbot/command/ssl.rb +43 -0
  21. data/lib/turbot/command/status.rb +51 -0
  22. data/lib/turbot/command/update.rb +47 -0
  23. data/lib/turbot/command/version.rb +23 -0
  24. data/lib/turbot/command.rb +304 -0
  25. data/lib/turbot/deprecated/help.rb +38 -0
  26. data/lib/turbot/deprecated.rb +5 -0
  27. data/lib/turbot/distribution.rb +9 -0
  28. data/lib/turbot/errors.rb +28 -0
  29. data/lib/turbot/excon.rb +11 -0
  30. data/lib/turbot/helpers/log_displayer.rb +70 -0
  31. data/lib/turbot/helpers/pg_dump_restore.rb +115 -0
  32. data/lib/turbot/helpers/turbot_postgresql.rb +213 -0
  33. data/lib/turbot/helpers.rb +521 -0
  34. data/lib/turbot/plugin.rb +165 -0
  35. data/lib/turbot/updater.rb +171 -0
  36. data/lib/turbot/version.rb +3 -0
  37. data/lib/turbot.rb +19 -0
  38. data/lib/vendor/turbot/okjson.rb +598 -0
  39. data/spec/helper/legacy_help.rb +16 -0
  40. data/spec/helper/pg_dump_restore_spec.rb +67 -0
  41. data/spec/schemas/dummy_schema.json +12 -0
  42. data/spec/spec.opts +1 -0
  43. data/spec/spec_helper.rb +220 -0
  44. data/spec/support/display_message_matcher.rb +49 -0
  45. data/spec/support/dummy_api.rb +120 -0
  46. data/spec/support/openssl_mock_helper.rb +8 -0
  47. data/spec/support/organizations_mock_helper.rb +11 -0
  48. data/spec/turbot/auth_spec.rb +214 -0
  49. data/spec/turbot/client/pgbackups_spec.rb +43 -0
  50. data/spec/turbot/client/rendezvous_spec.rb +62 -0
  51. data/spec/turbot/client/ssl_endpoint_spec.rb +48 -0
  52. data/spec/turbot/client/turbot_postgresql_spec.rb +71 -0
  53. data/spec/turbot/client_spec.rb +548 -0
  54. data/spec/turbot/command/auth_spec.rb +38 -0
  55. data/spec/turbot/command/base_spec.rb +66 -0
  56. data/spec/turbot/command/bots_spec.rb +54 -0
  57. data/spec/turbot/command/config_spec.rb +143 -0
  58. data/spec/turbot/command/help_spec.rb +90 -0
  59. data/spec/turbot/command/keys_spec.rb +117 -0
  60. data/spec/turbot/command/logs_spec.rb +60 -0
  61. data/spec/turbot/command/status_spec.rb +48 -0
  62. data/spec/turbot/command/version_spec.rb +16 -0
  63. data/spec/turbot/command_spec.rb +131 -0
  64. data/spec/turbot/helpers/turbot_postgresql_spec.rb +181 -0
  65. data/spec/turbot/helpers_spec.rb +48 -0
  66. data/spec/turbot/plugin_spec.rb +172 -0
  67. data/spec/turbot/updater_spec.rb +44 -0
  68. data/templates/manifest.json +7 -0
  69. data/templates/scraper.py +5 -0
  70. data/templates/scraper.rb +6 -0
  71. metadata +199 -0
@@ -0,0 +1,521 @@
1
+ require "vendor/turbot/okjson"
2
+
3
+ module Turbot
4
+ module Helpers
5
+
6
+ extend self
7
+
8
+ def home_directory
9
+ running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME']
10
+ end
11
+
12
+ def running_on_windows?
13
+ RUBY_PLATFORM =~ /mswin32|mingw32/
14
+ end
15
+
16
+ def running_on_a_mac?
17
+ RUBY_PLATFORM =~ /-darwin\d/
18
+ end
19
+
20
+ def display(msg="", new_line=true)
21
+ if new_line
22
+ puts(msg)
23
+ else
24
+ print(msg)
25
+ end
26
+ $stdout.flush
27
+ end
28
+
29
+ def redisplay(line, line_break = false)
30
+ display("\r\e[0K#{line}", line_break)
31
+ end
32
+
33
+ def deprecate(message)
34
+ display "WARNING: #{message}"
35
+ end
36
+
37
+ def confirm(message="Are you sure you wish to continue? (y/n)")
38
+ display("#{message} ", false)
39
+ ['y', 'yes'].include?(ask.downcase)
40
+ end
41
+
42
+ def confirm_command(bot_to_confirm = bot, message=nil)
43
+ if confirmed_bot = Turbot::Command.current_options[:confirm]
44
+ unless confirmed_bot == bot_to_confirm
45
+ raise(Turbot::Command::CommandFailed, "Confirmed bot #{confirmed_bot} did not match the selected bot #{bot_to_confirm}.")
46
+ end
47
+ return true
48
+ else
49
+ display
50
+ message ||= "WARNING: Destructive Action\nThis command will affect the bot: #{bot_to_confirm}"
51
+ message << "\nTo proceed, type \"#{bot_to_confirm}\" or re-run this command with --confirm #{bot_to_confirm}"
52
+ output_with_bang(message)
53
+ display
54
+ display "> ", false
55
+ if ask.downcase != bot_to_confirm
56
+ error("Confirmation did not match #{bot_to_confirm}. Aborted.")
57
+ else
58
+ true
59
+ end
60
+ end
61
+ end
62
+
63
+ def format_date(date)
64
+ date = Time.parse(date).utc if date.is_a?(String)
65
+ date.strftime("%Y-%m-%d %H:%M %Z").gsub('GMT', 'UTC')
66
+ end
67
+
68
+ def ask
69
+ $stdin.gets.to_s.strip
70
+ end
71
+
72
+ def shell(cmd)
73
+ FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
74
+ end
75
+
76
+ def run_command(command, args=[])
77
+ Turbot::Command.run(command, args)
78
+ end
79
+
80
+ def retry_on_exception(*exceptions)
81
+ retry_count = 0
82
+ begin
83
+ yield
84
+ rescue *exceptions => ex
85
+ raise ex if retry_count >= 3
86
+ sleep 3
87
+ retry_count += 1
88
+ retry
89
+ end
90
+ end
91
+
92
+ def has_git?
93
+ %x{ git --version }
94
+ $?.success?
95
+ end
96
+
97
+ def git(args)
98
+ return "" unless has_git?
99
+ flattened_args = [args].flatten.compact.join(" ")
100
+ %x{ git #{flattened_args} 2>&1 }.strip
101
+ end
102
+
103
+ def time_ago(since)
104
+ if since.is_a?(String)
105
+ since = Time.parse(since)
106
+ end
107
+
108
+ elapsed = Time.now - since
109
+
110
+ message = since.strftime("%Y/%m/%d %H:%M:%S")
111
+ if elapsed <= 60
112
+ message << " (~ #{elapsed.floor}s ago)"
113
+ elsif elapsed <= (60 * 60)
114
+ message << " (~ #{(elapsed / 60).floor}m ago)"
115
+ elsif elapsed <= (60 * 60 * 25)
116
+ message << " (~ #{(elapsed / 60 / 60).floor}h ago)"
117
+ end
118
+ message
119
+ end
120
+
121
+ def truncate(text, length)
122
+ if text.size > length
123
+ text[0, length - 2] + '..'
124
+ else
125
+ text
126
+ end
127
+ end
128
+
129
+ @@kb = 1024
130
+ @@mb = 1024 * @@kb
131
+ @@gb = 1024 * @@mb
132
+ def format_bytes(amount)
133
+ amount = amount.to_i
134
+ return '(empty)' if amount == 0
135
+ return amount if amount < @@kb
136
+ return "#{(amount / @@kb).round}k" if amount < @@mb
137
+ return "#{(amount / @@mb).round}M" if amount < @@gb
138
+ return "#{(amount / @@gb).round}G"
139
+ end
140
+
141
+ def quantify(string, num)
142
+ "%d %s" % [ num, num.to_i == 1 ? string : "#{string}s" ]
143
+ end
144
+
145
+ def create_git_remote(remote, url)
146
+ return if git('remote').split("\n").include?(remote)
147
+ return unless File.exists?(".git")
148
+ git "remote add #{remote} #{url}"
149
+ display "Git remote #{remote} added"
150
+ end
151
+
152
+ def longest(items)
153
+ items.map { |i| i.to_s.length }.sort.last
154
+ end
155
+
156
+ def display_table(objects, columns, headers)
157
+ lengths = []
158
+ columns.each_with_index do |column, index|
159
+ header = headers[index]
160
+ lengths << longest([header].concat(objects.map { |o| o[column].to_s }))
161
+ end
162
+ lines = lengths.map {|length| "-" * length}
163
+ lengths[-1] = 0 # remove padding from last column
164
+ display_row headers, lengths
165
+ display_row lines, lengths
166
+ objects.each do |row|
167
+ display_row columns.map { |column| row[column] }, lengths
168
+ end
169
+ end
170
+
171
+ def display_row(row, lengths)
172
+ row_data = []
173
+ row.zip(lengths).each do |column, length|
174
+ format = column.is_a?(Fixnum) ? "%#{length}s" : "%-#{length}s"
175
+ row_data << format % column
176
+ end
177
+ display(row_data.join(" "))
178
+ end
179
+
180
+ def json_encode(object)
181
+ Turbot::OkJson.encode(object)
182
+ rescue Turbot::OkJson::Error
183
+ nil
184
+ end
185
+
186
+ def json_decode(json)
187
+ Turbot::OkJson.decode(json)
188
+ rescue Turbot::OkJson::Error
189
+ nil
190
+ end
191
+
192
+ def set_buffer(enable)
193
+ with_tty do
194
+ if enable
195
+ `stty icanon echo`
196
+ else
197
+ `stty -icanon -echo`
198
+ end
199
+ end
200
+ end
201
+
202
+ def with_tty(&block)
203
+ return unless $stdin.isatty
204
+ begin
205
+ yield
206
+ rescue
207
+ # fails on windows
208
+ end
209
+ end
210
+
211
+ def get_terminal_environment
212
+ { "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`.strip, "LINES" => `tput lines`.strip }
213
+ rescue
214
+ { "TERM" => ENV["TERM"] }
215
+ end
216
+
217
+ def fail(message)
218
+ raise Turbot::Command::CommandFailed, message
219
+ end
220
+
221
+ ## DISPLAY HELPERS
222
+
223
+ def action(message, options={})
224
+ message = "#{message} in organzation #{org}" if options[:org]
225
+ display("#{message}... ", false)
226
+ Turbot::Helpers.error_with_failure = true
227
+ ret = yield
228
+ Turbot::Helpers.error_with_failure = false
229
+ display((options[:success] || "done"), false)
230
+ if @status
231
+ display(", #{@status}", false)
232
+ @status = nil
233
+ end
234
+ display
235
+ ret
236
+ end
237
+
238
+ def status(message)
239
+ @status = message
240
+ end
241
+
242
+ def format_with_bang(message)
243
+ return '' if message.to_s.strip == ""
244
+ " ! " + message.split("\n").join("\n ! ")
245
+ end
246
+
247
+ def output_with_bang(message="", new_line=true)
248
+ return if message.to_s.strip == ""
249
+ display(format_with_bang(message), new_line)
250
+ end
251
+
252
+ def error(message)
253
+ if Turbot::Helpers.error_with_failure
254
+ display("failed")
255
+ Turbot::Helpers.error_with_failure = false
256
+ end
257
+ $stderr.puts(format_with_bang(message))
258
+ exit(1)
259
+ end
260
+
261
+ def self.error_with_failure
262
+ @@error_with_failure ||= false
263
+ end
264
+
265
+ def self.error_with_failure=(new_error_with_failure)
266
+ @@error_with_failure = new_error_with_failure
267
+ end
268
+
269
+ def self.included_into
270
+ @@included_into ||= []
271
+ end
272
+
273
+ def self.extended_into
274
+ @@extended_into ||= []
275
+ end
276
+
277
+ def self.included(base)
278
+ included_into << base
279
+ end
280
+
281
+ def self.extended(base)
282
+ extended_into << base
283
+ end
284
+
285
+ def display_header(message="", new_line=true)
286
+ return if message.to_s.strip == ""
287
+ display("=== " + message.to_s.split("\n").join("\n=== "), new_line)
288
+ end
289
+
290
+ def display_object(object)
291
+ case object
292
+ when Array
293
+ # list of objects
294
+ object.each do |item|
295
+ display_object(item)
296
+ end
297
+ when Hash
298
+ # if all values are arrays, it is a list with headers
299
+ # otherwise it is a single header with pairs of data
300
+ if object.values.all? {|value| value.is_a?(Array)}
301
+ object.keys.sort_by {|key| key.to_s}.each do |key|
302
+ display_header(key)
303
+ display_object(object[key])
304
+ hputs
305
+ end
306
+ end
307
+ else
308
+ hputs(object.to_s)
309
+ end
310
+ end
311
+
312
+ def hputs(string='')
313
+ Kernel.puts(string)
314
+ end
315
+
316
+ def hprint(string='')
317
+ Kernel.print(string)
318
+ $stdout.flush
319
+ end
320
+
321
+ def spinner(ticks)
322
+ %w(/ - \\ |)[ticks % 4]
323
+ end
324
+
325
+ def launchy(message, url)
326
+ action(message) do
327
+ require("launchy")
328
+ launchy = Launchy.open(url)
329
+ if launchy.respond_to?(:join)
330
+ launchy.join
331
+ end
332
+ end
333
+ end
334
+
335
+ # produces a printf formatter line for an array of items
336
+ # if an individual line item is an array, it will create columns
337
+ # that are lined-up
338
+ #
339
+ # line_formatter(["foo", "barbaz"]) # => "%-6s"
340
+ # line_formatter(["foo", "barbaz"], ["bar", "qux"]) # => "%-3s %-6s"
341
+ #
342
+ def line_formatter(array)
343
+ if array.any? {|item| item.is_a?(Array)}
344
+ cols = []
345
+ array.each do |item|
346
+ if item.is_a?(Array)
347
+ item.each_with_index { |val,idx| cols[idx] = [cols[idx]||0, (val || '').length].max }
348
+ end
349
+ end
350
+ cols.map { |col| "%-#{col}s" }.join(" ")
351
+ else
352
+ "%s"
353
+ end
354
+ end
355
+
356
+ def styled_array(array, options={})
357
+ fmt = line_formatter(array)
358
+ array = array.sort unless options[:sort] == false
359
+ array.each do |element|
360
+ display((fmt % element).rstrip)
361
+ end
362
+ display
363
+ end
364
+
365
+ def format_error(error, message='Turbot client internal error.')
366
+ formatted_error = []
367
+ formatted_error << " ! #{message}"
368
+ formatted_error << ' ! Report a bug at: https://github.com/openc/turbot-client/issues/new'
369
+ formatted_error << ''
370
+ formatted_error << " Error: #{error.message} (#{error.class})"
371
+ formatted_error << " Backtrace: #{error.backtrace.first}"
372
+ error.backtrace[1..-1].each do |line|
373
+ formatted_error << " #{line}"
374
+ end
375
+ if error.backtrace.length > 1
376
+ formatted_error << ''
377
+ end
378
+ command = ARGV.map do |arg|
379
+ if arg.include?(' ')
380
+ arg = %{"#{arg}"}
381
+ else
382
+ arg
383
+ end
384
+ end.join(' ')
385
+ formatted_error << " Command: turbot #{command}"
386
+ require 'turbot/auth'
387
+ unless Turbot::Auth.host == Turbot::Auth.default_host
388
+ formatted_error << " Host: #{Turbot::Auth.host}"
389
+ end
390
+ if http_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
391
+ formatted_error << " HTTP Proxy: #{http_proxy}"
392
+ end
393
+ if https_proxy = ENV['https_proxy'] || ENV['HTTPS_PROXY']
394
+ formatted_error << " HTTPS Proxy: #{https_proxy}"
395
+ end
396
+ plugins = Turbot::Plugin.list.sort
397
+ unless plugins.empty?
398
+ formatted_error << " Plugins: #{plugins.first}"
399
+ plugins[1..-1].each do |plugin|
400
+ formatted_error << " #{plugin}"
401
+ end
402
+ if plugins.length > 1
403
+ formatted_error << ''
404
+ $stderr.puts
405
+ end
406
+ end
407
+ formatted_error << " Version: #{Turbot.user_agent}"
408
+ formatted_error << "\n"
409
+ formatted_error.join("\n")
410
+ end
411
+
412
+ def styled_error(error, message='Turbot client internal error.')
413
+ if Turbot::Helpers.error_with_failure
414
+ display("failed")
415
+ Turbot::Helpers.error_with_failure = false
416
+ end
417
+ $stderr.puts(format_error(error, message))
418
+ end
419
+
420
+ def styled_header(header)
421
+ display("=== #{header}")
422
+ end
423
+
424
+ def styled_hash(hash, keys=nil)
425
+ max_key_length = hash.keys.map {|key| key.to_s.length}.max + 2
426
+ keys ||= hash.keys.sort {|x,y| x.to_s <=> y.to_s}
427
+ keys.each do |key|
428
+ case value = hash[key]
429
+ when Array
430
+ if value.empty?
431
+ next
432
+ else
433
+ elements = value.sort {|x,y| x.to_s <=> y.to_s}
434
+ display("#{key}: ".ljust(max_key_length), false)
435
+ display(elements[0])
436
+ elements[1..-1].each do |element|
437
+ display("#{' ' * max_key_length}#{element}")
438
+ end
439
+ if elements.length > 1
440
+ display
441
+ end
442
+ end
443
+ when nil
444
+ next
445
+ else
446
+ display("#{key}: ".ljust(max_key_length), false)
447
+ display(value)
448
+ end
449
+ end
450
+ end
451
+
452
+ def string_distance(first, last)
453
+ distances = [] # 0x0s
454
+ 0.upto(first.length) do |index|
455
+ distances << [index] + [0] * last.length
456
+ end
457
+ distances[0] = 0.upto(last.length).to_a
458
+ 1.upto(last.length) do |last_index|
459
+ 1.upto(first.length) do |first_index|
460
+ first_char = first[first_index - 1, 1]
461
+ last_char = last[last_index - 1, 1]
462
+ if first_char == last_char
463
+ distances[first_index][last_index] = distances[first_index - 1][last_index - 1] # noop
464
+ else
465
+ distances[first_index][last_index] = [
466
+ distances[first_index - 1][last_index], # deletion
467
+ distances[first_index][last_index - 1], # insertion
468
+ distances[first_index - 1][last_index - 1] # substitution
469
+ ].min + 1 # cost
470
+ if first_index > 1 && last_index > 1
471
+ first_previous_char = first[first_index - 2, 1]
472
+ last_previous_char = last[last_index - 2, 1]
473
+ if first_char == last_previous_char && first_previous_char == last_char
474
+ distances[first_index][last_index] = [
475
+ distances[first_index][last_index],
476
+ distances[first_index - 2][last_index - 2] + 1 # transposition
477
+ ].min
478
+ end
479
+ end
480
+ end
481
+ end
482
+ end
483
+ distances[first.length][last.length]
484
+ end
485
+
486
+ def suggestion(actual, possibilities)
487
+ distances = Hash.new {|hash,key| hash[key] = []}
488
+ possibilities.each do |suggestion|
489
+ distances[string_distance(actual, suggestion)] << suggestion
490
+ end
491
+ minimum_distance = distances.keys.min
492
+ if minimum_distance < 4
493
+ suggestions = distances[minimum_distance].sort
494
+ if suggestions.length == 1
495
+ "Perhaps you meant `#{suggestions.first}`."
496
+ else
497
+ "Perhaps you meant #{suggestions[0...-1].map {|suggestion| "`#{suggestion}`"}.join(', ')} or `#{suggestions.last}`."
498
+ end
499
+ else
500
+ nil
501
+ end
502
+ end
503
+
504
+ def org_host
505
+ ENV["TURBOT_ORG_HOST"] || default_org_host
506
+ end
507
+
508
+ def default_org_host
509
+ "turbotmanager.com"
510
+ end
511
+
512
+ def org? email
513
+ email =~ /^.*@#{org_host}$/
514
+ end
515
+
516
+ def bot_owner email
517
+ org?(email) ? email.gsub(/^(.*)@#{org_host}$/,'\1') : email
518
+ end
519
+
520
+ end
521
+ end
@@ -0,0 +1,165 @@
1
+ # based on the Rails Plugin
2
+
3
+ module Turbot
4
+ class Plugin
5
+ include Turbot::Helpers
6
+ extend Turbot::Helpers
7
+
8
+ class ErrorUpdatingSymlinkPlugin < StandardError; end
9
+
10
+ DEPRECATED_PLUGINS = %w(
11
+ turbot-cedar
12
+ turbot-certs
13
+ turbot-credentials
14
+ turbot-dyno-size
15
+ turbot-kill
16
+ turbot-labs
17
+ turbot-logging
18
+ turbot-netrc
19
+ turbot-pgdumps
20
+ turbot-postgresql
21
+ turbot-releases
22
+ turbot-shared-postgresql
23
+ turbot-sql-console
24
+ turbot-status
25
+ turbot-stop
26
+ turbot-suggest
27
+ pgbackups-automate
28
+ pgcmd
29
+ turbot-fork
30
+ turbot-orgs
31
+ )
32
+
33
+ attr_reader :name, :uri
34
+
35
+ def self.directory
36
+ File.expand_path("#{home_directory}/.turbot/plugins")
37
+ end
38
+
39
+ def self.list
40
+ Dir["#{directory}/*"].sort.map do |folder|
41
+ File.basename(folder)
42
+ end
43
+ end
44
+
45
+ def self.load!
46
+ list.each do |plugin|
47
+ check_for_deprecation(plugin)
48
+ next if skip_plugins.include?(plugin)
49
+ load_plugin(plugin)
50
+ end
51
+ # check to see if we are using ddollar/turbot-accounts
52
+ if list.include?('turbot-accounts') && Turbot::Auth.methods.include?(:fetch_from_account)
53
+ # setup netrc to match the default, if one exists
54
+ if default_account = %x{ git config turbot.account }.chomp
55
+ account = Turbot::Auth.extract_account rescue nil
56
+ if account && Turbot::Auth.read_credentials != [Turbot::Auth.user, Turbot::Auth.password]
57
+ Turbot::Auth.credentials = [Turbot::Auth.user, Turbot::Auth.password]
58
+ Turbot::Auth.write_credentials
59
+ load("#{File.dirname(__FILE__)}/command/accounts.rb")
60
+ # kill memoization in case '--account' was passed
61
+ Turbot::Auth.instance_variable_set(:@account, nil)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def self.load_plugin(plugin)
68
+ begin
69
+ folder = "#{self.directory}/#{plugin}"
70
+ $: << "#{folder}/lib" if File.directory? "#{folder}/lib"
71
+ load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
72
+ rescue ScriptError, StandardError => error
73
+ styled_error(error, "Unable to load plugin #{plugin}.")
74
+ false
75
+ end
76
+ end
77
+
78
+ def self.remove_plugin(plugin)
79
+ FileUtils.rm_rf("#{self.directory}/#{plugin}")
80
+ end
81
+
82
+ def self.check_for_deprecation(plugin)
83
+ return unless STDIN.isatty
84
+
85
+ if DEPRECATED_PLUGINS.include?(plugin)
86
+ if confirm "The plugin #{plugin} has been deprecated. Would you like to remove it? (y/N)"
87
+ remove_plugin(plugin)
88
+ end
89
+ end
90
+ end
91
+
92
+ def self.skip_plugins
93
+ @skip_plugins ||= ENV["SKIP_PLUGINS"].to_s.split(/[ ,]/)
94
+ end
95
+
96
+ def initialize(uri)
97
+ @uri = uri
98
+ guess_name(uri)
99
+ end
100
+
101
+ def to_s
102
+ name
103
+ end
104
+
105
+ def path
106
+ "#{self.class.directory}/#{name}"
107
+ end
108
+
109
+ def install
110
+ if File.directory?(path)
111
+ uninstall
112
+ end
113
+ FileUtils.mkdir_p(self.class.directory)
114
+ Dir.chdir(self.class.directory) do
115
+ git("clone #{uri}")
116
+ unless $?.success?
117
+ FileUtils.rm_rf path
118
+ return false
119
+ end
120
+ end
121
+ true
122
+ end
123
+
124
+ def uninstall
125
+ ensure_plugin_exists
126
+ FileUtils.rm_r(path)
127
+ end
128
+
129
+ def update
130
+ ensure_plugin_exists
131
+ if File.symlink?(path)
132
+ raise Turbot::Plugin::ErrorUpdatingSymlinkPlugin
133
+ else
134
+ Dir.chdir(path) do
135
+ unless git('config --get branch.master.remote').empty?
136
+ message = git("pull")
137
+ unless $?.success?
138
+ error("Unable to update #{name}.\n" + message)
139
+ end
140
+ else
141
+ error(<<-ERROR)
142
+ #{name} is a legacy plugin installation.
143
+ Enable updating by reinstalling with `turbot plugins:install`.
144
+ ERROR
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def ensure_plugin_exists
153
+ unless File.directory?(path)
154
+ error("#{name} plugin not found.")
155
+ end
156
+ end
157
+
158
+ def guess_name(url)
159
+ @name = File.basename(url)
160
+ @name = File.basename(File.dirname(url)) if @name.empty?
161
+ @name.gsub!(/\.git$/, '') if @name =~ /\.git$/
162
+ end
163
+
164
+ end
165
+ end