turbot 0.1.36 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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