turbot 0.1.36 → 0.2.3

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +15 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/README.md +44 -25
  9. data/Rakefile +16 -0
  10. data/appveyor.yml +35 -0
  11. data/bin/turbot +2 -16
  12. data/data/schema.json +134 -0
  13. data/{templates → data/templates}/LICENSE.txt +0 -0
  14. data/{templates → data/templates}/manifest.json +0 -0
  15. data/{templates → data/templates}/python/scraper.py +0 -0
  16. data/{templates → data/templates}/ruby/scraper.rb +0 -0
  17. data/dist/deb.rake +32 -0
  18. data/dist/gem.rake +16 -0
  19. data/dist/manifest.rake +9 -0
  20. data/dist/pkg.rake +60 -0
  21. data/dist/resources/deb/control +10 -0
  22. data/dist/resources/deb/postinst +45 -0
  23. data/dist/resources/deb/turbot +25 -0
  24. data/dist/resources/deb/turbot-release-key.txt +30 -0
  25. data/dist/resources/pkg/Distribution.erb +15 -0
  26. data/dist/resources/pkg/PackageInfo.erb +6 -0
  27. data/dist/resources/pkg/postinstall +45 -0
  28. data/dist/resources/pkg/turbot +24 -0
  29. data/dist/resources/tgz/turbot +24 -0
  30. data/dist/rpm.rake +35 -0
  31. data/dist/tgz.rake +26 -0
  32. data/dist/zip.rake +40 -0
  33. data/lib/turbot.rb +18 -15
  34. data/lib/turbot/cli.rb +10 -27
  35. data/lib/turbot/command.rb +59 -212
  36. data/lib/turbot/command/auth.rb +72 -34
  37. data/lib/turbot/command/base.rb +22 -61
  38. data/lib/turbot/command/bots.rb +251 -300
  39. data/lib/turbot/command/help.rb +57 -110
  40. data/lib/turbot/command/version.rb +6 -10
  41. data/lib/turbot/handlers/base_handler.rb +21 -0
  42. data/lib/turbot/handlers/dump_handler.rb +10 -0
  43. data/lib/turbot/handlers/preview_handler.rb +30 -0
  44. data/lib/turbot/handlers/validation_handler.rb +17 -0
  45. data/lib/turbot/helpers.rb +14 -482
  46. data/lib/turbot/helpers/api_helper.rb +41 -0
  47. data/lib/turbot/helpers/netrc_helper.rb +66 -0
  48. data/lib/turbot/helpers/shell_helper.rb +36 -0
  49. data/lib/turbot/version.rb +1 -1
  50. data/spec/fixtures/bad_permissions +0 -0
  51. data/spec/fixtures/empty +0 -0
  52. data/spec/fixtures/netrc +6 -0
  53. data/spec/spec_helper.rb +17 -219
  54. data/spec/support/bot_helper.rb +102 -0
  55. data/spec/support/command_helper.rb +20 -0
  56. data/spec/support/custom_matchers.rb +5 -0
  57. data/spec/support/fixture_helper.rb +9 -0
  58. data/spec/support/netrc_helper.rb +21 -0
  59. data/spec/turbot/command/auth_spec.rb +202 -20
  60. data/spec/turbot/command/base_spec.rb +22 -58
  61. data/spec/turbot/command/bots_spec.rb +580 -89
  62. data/spec/turbot/command/help_spec.rb +32 -75
  63. data/spec/turbot/command/version_spec.rb +11 -10
  64. data/spec/turbot/command_spec.rb +55 -87
  65. data/spec/turbot/helpers_spec.rb +28 -44
  66. data/turbot.gemspec +31 -0
  67. metadata +88 -178
  68. data/data/cacert.pem +0 -3988
  69. data/lib/turbot/auth.rb +0 -315
  70. data/lib/turbot/client.rb +0 -757
  71. data/lib/turbot/client/cisaurus.rb +0 -25
  72. data/lib/turbot/client/pgbackups.rb +0 -113
  73. data/lib/turbot/client/rendezvous.rb +0 -111
  74. data/lib/turbot/client/ssl_endpoint.rb +0 -25
  75. data/lib/turbot/client/turbot_postgresql.rb +0 -148
  76. data/lib/turbot/command/ssl.rb +0 -43
  77. data/lib/turbot/deprecated.rb +0 -5
  78. data/lib/turbot/deprecated/help.rb +0 -38
  79. data/lib/turbot/distribution.rb +0 -9
  80. data/lib/turbot/errors.rb +0 -28
  81. data/lib/turbot/excon.rb +0 -11
  82. data/lib/turbot/helpers/log_displayer.rb +0 -70
  83. data/lib/turbot/helpers/pg_dump_restore.rb +0 -115
  84. data/lib/turbot/helpers/turbot_postgresql.rb +0 -213
  85. data/lib/turbot/plugin.rb +0 -165
  86. data/lib/turbot/updater.rb +0 -171
  87. data/lib/vendor/turbot/okjson.rb +0 -598
  88. data/spec/helper/legacy_help.rb +0 -16
  89. data/spec/helper/pg_dump_restore_spec.rb +0 -67
  90. data/spec/spec.opts +0 -1
  91. data/spec/support/display_message_matcher.rb +0 -49
  92. data/spec/support/dummy_api.rb +0 -120
  93. data/spec/support/openssl_mock_helper.rb +0 -8
  94. data/spec/support/organizations_mock_helper.rb +0 -11
  95. data/spec/turbot/auth_spec.rb +0 -214
  96. data/spec/turbot/client/pgbackups_spec.rb +0 -43
  97. data/spec/turbot/client/rendezvous_spec.rb +0 -62
  98. data/spec/turbot/client/ssl_endpoint_spec.rb +0 -48
  99. data/spec/turbot/client/turbot_postgresql_spec.rb +0 -71
  100. data/spec/turbot/client_spec.rb +0 -548
  101. data/spec/turbot/helpers/turbot_postgresql_spec.rb +0 -181
  102. data/spec/turbot/plugin_spec.rb +0 -172
  103. data/spec/turbot/updater_spec.rb +0 -44
data/lib/turbot/auth.rb DELETED
@@ -1,315 +0,0 @@
1
- require "cgi"
2
- require "turbot"
3
- require "turbot/client"
4
- require "turbot/helpers"
5
-
6
- require "netrc"
7
-
8
- class Turbot::Auth
9
- class << self
10
- include Turbot::Helpers
11
-
12
- attr_accessor :credentials
13
-
14
- def api
15
- @api ||= begin
16
- Turbot::API.new(default_params.merge(:api_key => password))
17
- end
18
- end
19
-
20
- def client
21
- @client ||= begin
22
- client = Turbot::Client.new(user, password, host)
23
- client.on_warning { |msg| self.display("\n#{msg}\n\n") }
24
- client
25
- end
26
- end
27
-
28
- def login
29
- delete_credentials
30
- get_credentials
31
- end
32
-
33
- def logout
34
- delete_credentials
35
- end
36
-
37
- # will raise if not authenticated
38
- def check
39
- api.get_user
40
- end
41
-
42
- def default_host
43
- "http://turbot.opencorporates.com"
44
- end
45
-
46
- def git_host
47
- ENV['TURBOT_GIT_HOST'] || host
48
- end
49
-
50
- def host
51
- ENV['TURBOT_HOST'] || default_host
52
- end
53
-
54
- def reauthorize
55
- @credentials = ask_for_and_save_credentials
56
- end
57
-
58
- def user # :nodoc:
59
- get_credentials[0]
60
- end
61
-
62
- def password # :nodoc:
63
- get_credentials[1]
64
- end
65
-
66
- def api_key
67
- api.get_api_key
68
- end
69
-
70
- def api_key_for_credentials(user = get_credentials[0], password = get_credentials[1])
71
- api = Turbot::API.new(default_params)
72
- api.get_api_key_for_credentials(user, password)["api_key"]
73
- end
74
-
75
- def get_credentials # :nodoc:
76
- @credentials ||= (read_credentials || ask_for_and_save_credentials)
77
- end
78
-
79
- def delete_credentials
80
- if netrc
81
- netrc.delete("api.#{host}")
82
- netrc.delete("code.#{host}")
83
- netrc.save
84
- end
85
- @api, @client, @credentials = nil, nil
86
- end
87
-
88
- def netrc_path
89
- default = Netrc.default_path
90
- encrypted = default + ".gpg"
91
- if File.exists?(encrypted)
92
- encrypted
93
- else
94
- default
95
- end
96
- end
97
-
98
- def netrc # :nodoc:
99
- @netrc ||= begin
100
- File.exists?(netrc_path) && Netrc.read(netrc_path)
101
- rescue => error
102
- if error.message =~ /^Permission bits for/
103
- perm = File.stat(netrc_path).mode & 0777
104
- abort("Permissions #{perm} for '#{netrc_path}' are too open. You should run `chmod 0600 #{netrc_path}` so that your credentials are NOT accessible by others.")
105
- else
106
- raise error
107
- end
108
- end
109
- end
110
-
111
- def read_credentials
112
- if ENV['TURBOT_API_KEY']
113
- ['', ENV['TURBOT_API_KEY']]
114
- else
115
-
116
- # read netrc credentials if they exist
117
- if netrc
118
- # force migration of long api tokens (80 chars) to short ones (40)
119
- # #write_credentials rewrites both api.* and code.*
120
- credentials = netrc["api.#{host}"]
121
- if credentials && credentials[1].length > 40
122
- @credentials = [ credentials[0], credentials[1][0,40] ]
123
- write_credentials
124
- end
125
-
126
- netrc["api.#{host}"]
127
- end
128
- end
129
- end
130
-
131
- def write_credentials
132
- FileUtils.mkdir_p(File.dirname(netrc_path))
133
- FileUtils.touch(netrc_path)
134
- unless running_on_windows?
135
- FileUtils.chmod(0600, netrc_path)
136
- end
137
- netrc["api.#{host}"] = self.credentials
138
- netrc["code.#{host}"] = self.credentials
139
- netrc.save
140
- end
141
-
142
- def echo_off
143
- with_tty do
144
- system "stty -echo"
145
- end
146
- end
147
-
148
- def echo_on
149
- with_tty do
150
- system "stty echo"
151
- end
152
- end
153
-
154
- def ask_for_credentials
155
- puts "Enter your Turbot credentials."
156
-
157
- print "Email: "
158
- user = ask
159
-
160
- print "Password (typing will be hidden): "
161
- password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
162
-
163
- [user, api_key_for_credentials(user, password)]
164
- end
165
-
166
- def ask_for_password_on_windows
167
- require "Win32API"
168
- char = nil
169
- password = ''
170
-
171
- while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
172
- break if char == 10 || char == 13 # received carriage return or newline
173
- if char == 127 || char == 8 # backspace and delete
174
- password.slice!(-1, 1)
175
- else
176
- # windows might throw a -1 at us so make sure to handle RangeError
177
- (password << char.chr) rescue RangeError
178
- end
179
- end
180
- puts
181
- return password
182
- end
183
-
184
- def ask_for_password
185
- echo_off
186
- password = ask
187
- puts
188
- echo_on
189
- return password
190
- end
191
-
192
- def ask_for_and_save_credentials
193
- begin
194
- # ask for username and password, look up API key against API given these
195
- # In looking up the API key it also attempts to log the user in
196
- @credentials = ask_for_credentials
197
- # write these to a hidden file
198
- write_credentials
199
- check
200
- rescue RestClient::Unauthorized, Turbot::API::Errors::NotFound, Turbot::API::Errors::Unauthorized => e
201
- delete_credentials
202
- display "Authentication failed."
203
- retry if retry_login?
204
- exit 1
205
- rescue Exception => e
206
- delete_credentials
207
- raise e
208
- end
209
- # check_for_associated_ssh_key unless Turbot::Command.current_command == "keys:add"
210
- @credentials
211
- end
212
-
213
- def check_for_associated_ssh_key
214
- if api.get_ssh_keys.empty?
215
- associate_or_generate_ssh_key
216
- end
217
- end
218
-
219
- def associate_or_generate_ssh_key
220
- public_keys = Dir.glob("#{home_directory}/.ssh/*.pub").sort
221
-
222
- case public_keys.length
223
- when 0 then
224
- display "Could not find an existing public key."
225
- display "Would you like to generate one? [Yn] ", false
226
- unless ask.strip.downcase == "n"
227
- display "Generating new SSH public key."
228
- generate_ssh_key("id_rsa")
229
- associate_key("#{home_directory}/.ssh/id_rsa.pub")
230
- end
231
- when 1 then
232
- display "Found existing public key: #{public_keys.first}"
233
- associate_key(public_keys.first)
234
- else
235
- display "Found the following SSH public keys:"
236
- public_keys.each_with_index do |key, index|
237
- display "#{index+1}) #{File.basename(key)}"
238
- end
239
- display "Which would you like to use with your Turbot account? ", false
240
- choice = ask.to_i - 1
241
- chosen = public_keys[choice]
242
- if choice == -1 || chosen.nil?
243
- error("Invalid choice")
244
- end
245
- associate_key(chosen)
246
- end
247
- end
248
-
249
- def generate_ssh_key(keyfile)
250
- ssh_dir = File.join(home_directory, ".ssh")
251
- unless File.exists?(ssh_dir)
252
- FileUtils.mkdir_p ssh_dir
253
- unless running_on_windows?
254
- File.chmod(0700, ssh_dir)
255
- end
256
- end
257
- output = `ssh-keygen -t rsa -N "" -f \"#{home_directory}/.ssh/#{keyfile}\" 2>&1`
258
- if ! $?.success?
259
- error("Could not generate key: #{output}")
260
- end
261
- end
262
-
263
- def associate_key(key)
264
- action("Uploading SSH public key #{key}") do
265
- if File.exists?(key)
266
- api.post_key(File.read(key))
267
- else
268
- error("Could not upload SSH public key: key file '" + key + "' does not exist")
269
- end
270
- end
271
- end
272
-
273
- def retry_login?
274
- @login_attempts ||= 0
275
- @login_attempts += 1
276
- @login_attempts < 3
277
- end
278
-
279
- def verified_hosts
280
- %w( turbot.com turbot-shadow.com )
281
- end
282
-
283
- def base_host(host)
284
- parts = URI.parse(full_host(host)).host.split(".")
285
- return parts.first if parts.size == 1
286
- parts[-2..-1].join(".")
287
- end
288
-
289
- def full_host(host)
290
- (host =~ /^http/) ? host : "https://api.#{host}"
291
- end
292
-
293
- def verify_host?(host)
294
- hostname = base_host(host)
295
- verified = verified_hosts.include?(hostname)
296
- verified = false if ENV["TURBOT_SSL_VERIFY"] == "disable"
297
- verified
298
- end
299
-
300
- protected
301
-
302
- def default_params
303
- uri = URI.parse(full_host(host))
304
- {
305
- :headers => {
306
- 'User-Agent' => Turbot.user_agent
307
- },
308
- :host => uri.host,
309
- :port => uri.port,
310
- :scheme => uri.scheme,
311
- :ssl_verify_peer => verify_host?(host)
312
- }
313
- end
314
- end
315
- end
data/lib/turbot/client.rb DELETED
@@ -1,757 +0,0 @@
1
- require 'rexml/document'
2
- require 'uri'
3
- require 'time'
4
- require 'turbot/auth'
5
- require 'turbot/command'
6
- require 'turbot/helpers'
7
- require 'turbot/version'
8
- require 'turbot/client/ssl_endpoint'
9
-
10
- # A Ruby class to call the Turbot REST API. You might use this if you want to
11
- # manage your Turbot bots from within a Ruby program, such as Capistrano.
12
- #
13
- # Example:
14
- #
15
- # require 'turbot'
16
- # turbot = Turbot::Client.new('me@example.com', 'mypass')
17
- # turbot.create()
18
- #
19
- class Turbot::Client
20
-
21
- include Turbot::Helpers
22
- extend Turbot::Helpers
23
-
24
- def self.version
25
- Turbot::VERSION
26
- end
27
-
28
- def self.gem_version_string
29
- "turbot-gem/#{version}"
30
- end
31
-
32
- attr_accessor :host, :user, :password
33
-
34
- def initialize(user, password, host=Turbot::Auth.host)
35
- require 'rest_client'
36
- @user = user
37
- @password = password
38
- @host = host
39
- end
40
-
41
- def self.deprecate
42
- method = caller.first.split('`').last[0...-1]
43
- source = caller[1].split(' ').first[0...-3]
44
- $stderr.puts(" ! DEPRECATED: Turbot::Client##{method} is deprecated, please use the turbot-api gem.")
45
- $stderr.puts(" ! DEPRECATED: More information available at https://github.com/openc/turbot-client")
46
- $stderr.puts(" ! DEPRECATED: Deprecated method called from #{source}.")
47
- end
48
-
49
- def deprecate
50
- self.class.deprecate
51
- end
52
-
53
- def self.auth(user, password, host=Turbot::Auth.host)
54
- deprecate # 08/01/2012
55
- client = new(user, password, host)
56
- json_decode client.post('/login', { :username => user, :password => password }, :accept => 'json').to_s
57
- end
58
-
59
- # Show a list of bots which you are a collaborator on.
60
- def list
61
- deprecate # 07/26/2012
62
- doc = xml(get('/bots').to_s)
63
- doc.elements.to_a("//bots/bot").map do |a|
64
- name = a.elements.to_a("name").first
65
- owner = a.elements.to_a("owner").first
66
- [name.text, owner.text]
67
- end
68
- end
69
-
70
- # Show info such as mode, custom domain, and collaborators on an bot.
71
- def info(name_or_domain)
72
- deprecate # 07/26/2012
73
- raise ArgumentError.new("name_or_domain is required for info") unless name_or_domain
74
- name_or_domain = name_or_domain.gsub(/^(http:\/\/)?(www\.)?/, '')
75
- doc = xml(get("/bots/#{name_or_domain}").to_s)
76
- attrs = hash_from_xml_doc(doc)[:bot]
77
- attrs.merge!(:collaborators => list_collaborators(attrs[:name]))
78
- attrs.merge!(:addons => installed_addons(attrs[:name]))
79
- end
80
-
81
- # Create a new bot, with an optional name.
82
- def create(name=nil, options={})
83
- deprecate # 07/26/2012
84
- name = create_request(name, options)
85
- loop do
86
- break if create_complete?(name)
87
- sleep 1
88
- end
89
- name
90
- end
91
-
92
- def create_bot(name=nil, options={})
93
- deprecate # 07/26/2012
94
- options[:name] = name if name
95
- json_decode(post("/bots", { :bot => options }, :accept => "application/json").to_s)
96
- end
97
-
98
- def create_request(name=nil, options={})
99
- deprecate # 07/26/2012
100
- options[:name] = name if name
101
- xml(post('/bots', :bot => options).to_s).elements["//bot/name"].text
102
- end
103
-
104
- def create_complete?(name)
105
- deprecate # 07/26/2012
106
- put("/bots/#{name}/status", {}).code == 201
107
- end
108
-
109
- # Update an bot. Available attributes:
110
- # :name => rename the bot (changes http and git urls)
111
- def update(name, attributes)
112
- deprecate # 07/26/2012
113
- put("/bots/#{name}", :bot => attributes).to_s
114
- end
115
-
116
- # Destroy the bot permanently.
117
- def destroy(name)
118
- deprecate # 07/26/2012
119
- delete("/bots/#{name}").to_s
120
- end
121
-
122
- def maintenance(bot_name, mode)
123
- deprecate # 07/31/2012
124
- mode = mode == :on ? '1' : '0'
125
- post("/bots/#{bot_name}/server/maintenance", :maintenance_mode => mode).to_s
126
- end
127
-
128
- def config_vars(bot_name)
129
- deprecate # 07/27/2012
130
- json_decode get("/bots/#{bot_name}/config_vars", :accept => :json).to_s
131
- end
132
-
133
- def add_config_vars(bot_name, new_vars)
134
- deprecate # 07/27/2012
135
- put("/bots/#{bot_name}/config_vars", json_encode(new_vars), :accept => :json).to_s
136
- end
137
-
138
- def remove_config_var(bot_name, key)
139
- deprecate # 07/27/2012
140
- delete("/bots/#{bot_name}/config_vars/#{escape(key)}", :accept => :json).to_s
141
- end
142
-
143
- def clear_config_vars(bot_name)
144
- deprecate # 07/27/2012
145
- delete("/bots/#{bot_name}/config_vars").to_s
146
- end
147
-
148
- # Get a list of collaborators on the bot, returns an array of hashes each with :email
149
- def list_collaborators(bot_name)
150
- deprecate # 07/31/2012
151
- doc = xml(get("/bots/#{bot_name}/collaborators").to_s)
152
- doc.elements.to_a("//collaborators/collaborator").map do |a|
153
- { :email => a.elements['email'].text }
154
- end
155
- end
156
-
157
- # Invite a person by email address to collaborate on the bot.
158
- def add_collaborator(bot_name, email)
159
- deprecate # 07/31/2012
160
- xml(post("/bots/#{bot_name}/collaborators", { 'collaborator[email]' => email }).to_s)
161
- end
162
-
163
- # Remove a collaborator.
164
- def remove_collaborator(bot_name, email)
165
- deprecate # 07/31/2012
166
- delete("/bots/#{bot_name}/collaborators/#{escape(email)}").to_s
167
- end
168
-
169
- def list_domains(bot_name)
170
- deprecate # 08/02/2012
171
- doc = xml(get("/bots/#{bot_name}/domains").to_s)
172
- doc.elements.to_a("//domains/*").map do |d|
173
- attrs = { :domain => d.elements['domain'].text }
174
- if cert = d.elements['cert']
175
- attrs[:cert] = {
176
- :expires_at => Time.parse(cert.elements['expires-at'].text),
177
- :subject => cert.elements['subject'].text,
178
- :issuer => cert.elements['issuer'].text,
179
- }
180
- end
181
- attrs
182
- end
183
- end
184
-
185
- def add_domain(bot_name, domain)
186
- deprecate # 07/31/2012
187
- post("/bots/#{bot_name}/domains", domain).to_s
188
- end
189
-
190
- def remove_domain(bot_name, domain)
191
- deprecate # 07/31/2012
192
- raise ArgumentError.new("invalid domain: #{domain.inspect}") if domain.to_s.strip == ""
193
- delete("/bots/#{bot_name}/domains/#{domain}").to_s
194
- end
195
-
196
- def remove_domains(bot_name)
197
- deprecate # 07/31/2012
198
- delete("/bots/#{bot_name}/domains").to_s
199
- end
200
-
201
- # Get the list of ssh public keys for the current user.
202
- def keys
203
- deprecate # 07/31/2012
204
- doc = xml get('/user/keys').to_s
205
- doc.elements.to_a('//keys/key').map do |key|
206
- key.elements['contents'].text
207
- end
208
- end
209
-
210
- # Add an ssh public key to the current user.
211
- def add_key(key)
212
- deprecate # 07/31/2012
213
- post("/user/keys", key, { 'Content-Type' => 'text/ssh-authkey' }).to_s
214
- end
215
-
216
- # Remove an existing ssh public key from the current user.
217
- def remove_key(key)
218
- deprecate # 07/31/2012
219
- delete("/user/keys/#{escape(key)}").to_s
220
- end
221
-
222
- # Clear all keys on the current user.
223
- def remove_all_keys
224
- deprecate # 07/31/2012
225
- delete("/user/keys").to_s
226
- end
227
-
228
- # Retreive ps list for the given bot name.
229
- def ps(bot_name)
230
- deprecate # 07/31/2012
231
- json_decode get("/bots/#{bot_name}/ps", :accept => 'application/json').to_s
232
- end
233
-
234
- # Restart the bot servers.
235
- def restart(bot_name)
236
- deprecate # 07/31/2012
237
- delete("/bots/#{bot_name}/server").to_s
238
- end
239
-
240
- def dynos(bot_name)
241
- deprecate # 07/31/2012
242
- doc = xml(get("/bots/#{bot_name}").to_s)
243
- doc.elements["//bot/dynos"].text.to_i
244
- end
245
-
246
- def workers(bot_name)
247
- deprecate # 07/31/2012
248
- doc = xml(get("/bots/#{bot_name}").to_s)
249
- doc.elements["//bot/workers"].text.to_i
250
- end
251
-
252
- # Scales the web dynos.
253
- def set_dynos(bot_name, qty)
254
- deprecate # 07/31/2012
255
- put("/bots/#{bot_name}/dynos", :dynos => qty).to_s
256
- end
257
-
258
- # Scales the background dynos.
259
- def set_workers(bot_name, qty)
260
- deprecate # 07/31/2012
261
- put("/bots/#{bot_name}/workers", :workers => qty).to_s
262
- end
263
-
264
- def ps_run(bot, opts={})
265
- deprecate # 07/31/2012
266
- json_decode post("/bots/#{bot}/ps", opts, :accept => :json).to_s
267
- end
268
-
269
- def ps_scale(bot, opts={})
270
- deprecate # 07/31/2012
271
- Integer(post("/bots/#{bot}/ps/scale", opts).to_s)
272
- end
273
-
274
- def ps_restart(bot, opts={})
275
- deprecate # 07/31/2012
276
- post("/bots/#{bot}/ps/restart", opts)
277
- end
278
-
279
- def ps_stop(bot, opts={})
280
- deprecate # 07/31/2012
281
- post("/bots/#{bot}/ps/stop", opts)
282
- end
283
-
284
- def releases(bot)
285
- deprecate # 07/31/2012
286
- json_decode get("/bots/#{bot}/releases", :accept => :json).to_s
287
- end
288
-
289
- def release(bot, release)
290
- deprecate # 07/31/2012
291
- json_decode get("/bots/#{bot}/releases/#{release}", :accept => :json).to_s
292
- end
293
-
294
- def rollback(bot, release=nil)
295
- deprecate # 07/31/2012
296
- post("/bots/#{bot}/releases", :rollback => release)
297
- end
298
-
299
- # Fetch recent logs from the bot server.
300
- def logs(bot_name)
301
- deprecate # 07/31/2012
302
- get("/bots/#{bot_name}/logs").to_s
303
- end
304
-
305
- def list_features(bot)
306
- deprecate # 07/31/2012
307
- json_decode(get("features?bot=#{bot}", :accept => :json).to_s)
308
- end
309
-
310
- def get_feature(bot, name)
311
- deprecate # 07/31/2012
312
- json_decode get("features/#{name}?bot=#{bot}", :accept => :json).to_s
313
- end
314
-
315
- def enable_feature(bot, name)
316
- deprecate # 07/31/2012
317
- json_decode post("/features/#{name}?bot=#{bot}", :accept => :json).to_s
318
- end
319
-
320
- def disable_feature(bot, name)
321
- deprecate # 07/31/2012
322
- json_decode delete("/features/#{name}?bot=#{bot}", :accept => :json).to_s
323
- end
324
-
325
- # Get a list of stacks available to the bot, with the current one marked.
326
- def list_stacks(bot_name, options={})
327
- deprecate # 07/31/2012
328
- include_deprecated = options.delete(:include_deprecated) || false
329
-
330
- json_decode get("/bots/#{bot_name}/stack",
331
- :params => { :include_deprecated => include_deprecated },
332
- :accept => 'application/json'
333
- ).to_s
334
- end
335
-
336
- # Request a stack migration.
337
- def migrate_to_stack(bot_name, stack)
338
- deprecate # 07/31/2012
339
- put("/bots/#{bot_name}/stack", stack, :accept => 'text/plain').to_s
340
- end
341
-
342
- # Run a rake command on the Turbot bot and return output as a string
343
- def rake(bot_name, cmd)
344
- # deprecated by virtue of start deprecation 08/02/2012
345
- start(bot_name, "rake #{cmd}", :attached).to_s
346
- end
347
-
348
- class Service
349
- attr_accessor :attached
350
-
351
- def initialize(client, bot)
352
- require 'rest_client'
353
- @client = client
354
- @bot = bot
355
- end
356
-
357
- # start the service
358
- def start(command, attached=false)
359
- @attached = attached
360
- @response = @client.post(
361
- "/bots/#{@bot}/services",
362
- command,
363
- :content_type => 'text/plain'
364
- )
365
- @next_chunk = @response.to_s
366
- @interval = 0
367
- self
368
- rescue RestClient::RequestFailed => e
369
- raise AppCrashed, e.http_body if e.http_code == 502
370
- raise
371
- end
372
-
373
- # Does the service have any remaining output?
374
- def end_of_stream?
375
- @next_chunk.nil?
376
- end
377
-
378
- # Read the next chunk of output.
379
- def read
380
- chunk = @client.get(@next_chunk)
381
- if chunk.headers[:location].nil? && chunk.code != 204
382
- # no more chunks
383
- @next_chunk = nil
384
- chunk.to_s
385
- elsif chunk.to_s == ''
386
- # assume no content and back off
387
- @interval = 2
388
- ''
389
- elsif location = chunk.headers[:location]
390
- # some data read and next chunk available
391
- @next_chunk = location
392
- @interval = 0
393
- chunk.to_s
394
- end
395
- end
396
-
397
- # Iterate over all output chunks until EOF is reached.
398
- def each
399
- until end_of_stream?
400
- sleep(@interval)
401
- output = read
402
- yield output unless output.empty?
403
- end
404
- end
405
-
406
- # All output as a string
407
- def to_s
408
- buf = []
409
- each { |part| buf << part }
410
- buf.join
411
- end
412
- end
413
-
414
- # Run a service. If Responds to #each and yields output as it's received.
415
- def start(bot_name, command, attached=false)
416
- deprecate # 08/02/2012
417
- service = Service.new(self, bot_name)
418
- service.start(command, attached)
419
- end
420
-
421
- def add_ssl(bot_name, pem, key)
422
- json_decode(post("/bots/#{bot_name}/ssl", :pem => pem, :key => key).to_s)
423
- end
424
-
425
- def remove_ssl(bot_name, domain)
426
- delete("/bots/#{bot_name}/domains/#{domain}/ssl").to_s
427
- end
428
-
429
- def clear_ssl(bot_name)
430
- delete("/bots/#{bot_name}/ssl")
431
- end
432
-
433
- class AppCrashed < RuntimeError; end
434
-
435
- # support for console sessions
436
- class ConsoleSession
437
- def initialize(id, bot, client)
438
- require 'rest_client'
439
- @id = id; @bot = bot; @client = client
440
- end
441
- def run(cmd)
442
- @client.run_console_command("/bots/#{@bot}/consoles/#{@id}/command", cmd, "=> ")
443
- end
444
- end
445
-
446
- # Execute a one-off console command, or start a new console tty session if
447
- # cmd is nil.
448
- def console(bot_name, cmd=nil)
449
- if block_given?
450
- id = post("/bots/#{bot_name}/consoles").to_s
451
- yield ConsoleSession.new(id, bot_name, self)
452
- delete("/bots/#{bot_name}/consoles/#{id}").to_s
453
- else
454
- run_console_command("/bots/#{bot_name}/console", cmd)
455
- end
456
- rescue RestClient::BadGateway => e
457
- raise(AppCrashed, <<-ERROR)
458
- Unable to attach to a dyno to open a console session.
459
- Your application may have crashed.
460
- Check the output of "turbot ps" and "turbot logs" for more information.
461
- ERROR
462
- end
463
-
464
- # internal method to run console commands formatting the output
465
- def run_console_command(url, command, prefix=nil)
466
- output = post(url, { :command => command }, :accept => "text/plain").to_s
467
- return output unless prefix
468
- if output.include?("\n")
469
- lines = output.split("\n")
470
- (lines[0..-2] << "#{prefix}#{lines.last}").join("\n")
471
- else
472
- prefix + output
473
- end
474
- rescue RestClient::RequestFailed => e
475
- if e.http_code == 422
476
- Turbot::Command.extract_error(e.http_body, :raw => true)
477
- else
478
- raise e
479
- end
480
- end
481
-
482
- def read_logs(bot_name, options=[])
483
- query = "&" + options.join("&") unless options.empty?
484
- url = get("/bots/#{bot_name}/logs?logplex=true#{query}").to_s
485
- if url == 'Use old logs'
486
- puts get("/bots/#{bot_name}/logs").to_s
487
- else
488
- uri = URI.parse(url);
489
-
490
- if uri.scheme == 'https'
491
- proxy = https_proxy
492
- else
493
- proxy = http_proxy
494
- end
495
-
496
- if proxy
497
- proxy_uri = URI.parse(proxy)
498
- http = Net::HTTP.new(uri.host, uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
499
- else
500
- http = Net::HTTP.new(uri.host, uri.port)
501
- end
502
-
503
- if uri.scheme == 'https'
504
- http.use_ssl = true
505
- if ENV["TURBOT_SSL_VERIFY"] == "disable"
506
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
507
- else
508
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
509
- http.ca_file = local_ca_file
510
- http.verify_callback = lambda do |preverify_ok, ssl_context|
511
- if (!preverify_ok) || ssl_context.error != 0
512
- error "WARNING: Unable to verify SSL certificate for #{host}\nTo disable SSL verification, run with TURBOT_SSL_VERIFY=disable"
513
- end
514
- true
515
- end
516
- end
517
- end
518
-
519
- http.read_timeout = 60 * 60 * 24
520
-
521
- begin
522
- http.start do
523
- http.request_get(uri.path + (uri.query ? "?" + uri.query : "")) do |response|
524
- error(response.message) if response.is_a? Net::HTTPServerError
525
- response["Tail-warning"] && $stderr.puts(response["X-Turbot-Warning"])
526
- response.read_body do |chunk|
527
- yield chunk
528
- end
529
- end
530
- end
531
- rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
532
- error("Could not connect to logging service")
533
- rescue Timeout::Error, EOFError
534
- error("\nRequest timed out")
535
- end
536
- end
537
- end
538
-
539
- def list_drains(bot_name)
540
- get("/bots/#{bot_name}/logs/drains").to_s
541
- end
542
-
543
- def add_drain(bot_name, url)
544
- post("/bots/#{bot_name}/logs/drains", "url=#{CGI.escape(url)}").to_s
545
- end
546
-
547
- def remove_drain(bot_name, url)
548
- delete("/bots/#{bot_name}/logs/drains?url=#{CGI.escape(url)}").to_s
549
- end
550
-
551
- def addons(filters = {})
552
- url = "/addons"
553
- params = filters.map{|k,v| "#{k}=#{v}"}.join("&")
554
- params = nil if params.empty?
555
- json_decode get([url,params].compact.join("?"), :accept => 'application/json').to_s
556
- end
557
-
558
- def installed_addons(bot_name)
559
- json_decode get("/bots/#{bot_name}/addons", :accept => 'application/json').to_s
560
- end
561
-
562
- def install_addon(bot_name, addon, config={})
563
- configure_addon :install, bot_name, addon, config
564
- end
565
-
566
- def upgrade_addon(bot_name, addon, config={})
567
- configure_addon :upgrade, bot_name, addon, config
568
- end
569
- alias_method :downgrade_addon, :upgrade_addon
570
-
571
- def uninstall_addon(bot_name, addon, options={})
572
- configure_addon :uninstall, bot_name, addon, options
573
- end
574
-
575
- def httpcache_purge(bot_name)
576
- delete("/bots/#{bot_name}/httpcache").to_s
577
- end
578
-
579
- def on_warning(&blk)
580
- @warning_callback = blk
581
- end
582
-
583
- ##################
584
-
585
- def resource(uri, options={})
586
- RestClient.proxy = case URI.parse(realize_full_uri(uri)).scheme
587
- when "http"
588
- http_proxy
589
- when "https"
590
- https_proxy
591
- end
592
- RestClient::Resource.new(realize_full_uri(uri), options.merge(:user => user, :password => password))
593
- end
594
-
595
- def get(uri, extra_headers={}) # :nodoc:
596
- process(:get, uri, extra_headers)
597
- end
598
-
599
- def post(uri, payload="", extra_headers={}) # :nodoc:
600
- process(:post, uri, extra_headers, payload)
601
- end
602
-
603
- def put(uri, payload, extra_headers={}) # :nodoc:
604
- process(:put, uri, extra_headers, payload)
605
- end
606
-
607
- def delete(uri, extra_headers={}) # :nodoc:
608
- process(:delete, uri, extra_headers)
609
- end
610
-
611
- def process(method, uri, extra_headers={}, payload=nil)
612
- headers = turbot_headers.merge(extra_headers)
613
- args = [method, payload, headers].compact
614
-
615
- resource_options = default_resource_options_for_uri(uri)
616
-
617
- begin
618
- response = resource(uri, resource_options).send(*args)
619
- rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
620
- host = URI.parse(realize_full_uri(uri)).host
621
- error "Unable to connect to #{host}"
622
- rescue RestClient::SSLCertificateNotVerified => ex
623
- host = URI.parse(realize_full_uri(uri)).host
624
- error "WARNING: Unable to verify SSL certificate for #{host}\nTo disable SSL verification, run with TURBOT_SSL_VERIFY=disable"
625
- end
626
-
627
- extract_warning(response)
628
- response
629
- end
630
-
631
- def extract_warning(response)
632
- return unless response
633
- if response.headers[:x_turbot_warning] && @warning_callback
634
- warning = response.headers[:x_turbot_warning]
635
- @displayed_warnings ||= {}
636
- unless @displayed_warnings[warning]
637
- @warning_callback.call(warning)
638
- @displayed_warnings[warning] = true
639
- end
640
- end
641
- end
642
-
643
- def turbot_headers # :nodoc:
644
- {
645
- 'X-Turbot-API-Version' => '2',
646
- 'User-Agent' => Turbot.user_agent,
647
- 'X-Ruby-Version' => RUBY_VERSION,
648
- 'X-Ruby-Platform' => RUBY_PLATFORM
649
- }
650
- end
651
-
652
- def xml(raw) # :nodoc:
653
- REXML::Document.new(raw)
654
- end
655
-
656
- def escape(value) # :nodoc:
657
- escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
658
- escaped.gsub('.', '%2E') # not covered by the previous URI.escape
659
- end
660
-
661
- module JSON
662
- def self.parse(json)
663
- json_decode(json)
664
- end
665
- end
666
-
667
- private
668
-
669
- def configure_addon(action, bot_name, addon, config = {})
670
- response = update_addon action,
671
- addon_path(bot_name, addon),
672
- config
673
-
674
- json_decode(response.to_s) unless response.to_s.empty?
675
- end
676
-
677
- def addon_path(bot_name, addon)
678
- "/bots/#{bot_name}/addons/#{escape(addon)}"
679
- end
680
-
681
- def update_addon(action, path, config)
682
- params = { :config => config }
683
- bot = params[:config].delete(:confirm)
684
- headers = { :accept => 'application/json' }
685
- params.merge!(:confirm => bot) if bot
686
-
687
- case action
688
- when :install
689
- post path, params, headers
690
- when :upgrade
691
- put path, params, headers
692
- when :uninstall
693
- confirm = bot ? "confirm=#{bot}" : ''
694
- delete "#{path}?#{confirm}", headers
695
- end
696
- end
697
-
698
- def realize_full_uri(given)
699
- full_host = (host =~ /^http/) ? host : "https://api.#{host}"
700
- host = URI.parse(full_host)
701
- uri = URI.parse(given)
702
- uri.host ||= host.host
703
- uri.scheme ||= host.scheme || "https"
704
- uri.path = (uri.path[0..0] == "/") ? uri.path : "/#{uri.path}"
705
- uri.port = host.port if full_host =~ /\:\d+/
706
- uri.to_s
707
- end
708
-
709
- def default_resource_options_for_uri(uri)
710
- if ENV["TURBOT_SSL_VERIFY"] == "disable"
711
- {}
712
- elsif realize_full_uri(uri) =~ %r|^https://api.turbot.com|
713
- { :verify_ssl => OpenSSL::SSL::VERIFY_PEER, :ssl_ca_file => local_ca_file }
714
- else
715
- {}
716
- end
717
- end
718
-
719
- def local_ca_file
720
- File.expand_path("../../../data/cacert.pem", __FILE__)
721
- end
722
-
723
- def hash_from_xml_doc(elements)
724
- elements.inject({}) do |hash, e|
725
- next(hash) unless e.respond_to?(:children)
726
- hash.update(e.name.gsub("-","_").to_sym => case e.children.length
727
- when 0 then nil
728
- when 1 then e.text
729
- else hash_from_xml_doc(e.children)
730
- end)
731
- end
732
- end
733
-
734
- def http_proxy
735
- proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
736
- if proxy && !proxy.empty?
737
- unless /^[^:]+:\/\// =~ proxy
738
- proxy = "http://" + proxy
739
- end
740
- proxy
741
- else
742
- nil
743
- end
744
- end
745
-
746
- def https_proxy
747
- proxy = ENV['HTTPS_PROXY'] || ENV['https_proxy']
748
- if proxy && !proxy.empty?
749
- unless /^[^:]+:\/\// =~ proxy
750
- proxy = "https://" + proxy
751
- end
752
- proxy
753
- else
754
- nil
755
- end
756
- end
757
- end